diff --git a/key-wallet-ffi/FFI_API.md b/key-wallet-ffi/FFI_API.md index 6d8a16b73..40c09f365 100644 --- a/key-wallet-ffi/FFI_API.md +++ b/key-wallet-ffi/FFI_API.md @@ -852,7 +852,7 @@ 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: FFITransactionContext, block_height: c_uint, block_hash: *const u8, // 32 bytes if not null timestamp: u64, update_state: bool, result_out: *mut FFITransactionCheckResult, error: *mut FFIError,) -> bool +managed_wallet_check_transaction(managed_wallet: *mut FFIManagedWalletInfo, wallet: *mut FFIWallet, tx_bytes: *const u8, tx_len: usize, context_type: FFITransactionContext, block_info: FFIBlockInfo, update_state: bool, result_out: *mut FFITransactionCheckResult, error: *mut FFIError,) -> bool ``` **Description:** @@ -1300,7 +1300,7 @@ Build and sign a transaction using the wallet's managed info This is the recomm #### `wallet_check_transaction` ```c -wallet_check_transaction(wallet: *mut FFIWallet, tx_bytes: *const u8, tx_len: usize, context_type: FFITransactionContext, block_height: u32, block_hash: *const u8, // 32 bytes if not null timestamp: u64, update_state: bool, result_out: *mut FFITransactionCheckResult, error: *mut FFIError,) -> bool +wallet_check_transaction(wallet: *mut FFIWallet, tx_bytes: *const u8, tx_len: usize, context_type: FFITransactionContext, block_info: FFIBlockInfo, update_state: bool, result_out: *mut FFITransactionCheckResult, error: *mut FFIError,) -> bool ``` **Description:** diff --git a/key-wallet-ffi/src/managed_account.rs b/key-wallet-ffi/src/managed_account.rs index 9d5dd42e3..2b0e6e6da 100644 --- a/key-wallet-ffi/src/managed_account.rs +++ b/key-wallet-ffi/src/managed_account.rs @@ -11,7 +11,7 @@ use dashcore::hashes::Hash; use crate::address_pool::{FFIAddressPool, FFIAddressPoolType}; use crate::error::{FFIError, FFIErrorCode}; -use crate::types::FFIAccountType; +use crate::types::{FFIAccountType, FFIBlockInfo}; use crate::wallet_manager::FFIWalletManager; use crate::FFINetwork; use key_wallet::account::account_collection::{DashpayAccountKey, PlatformPaymentAccountKey}; @@ -666,12 +666,8 @@ pub struct FFITransactionRecord { pub txid: [u8; 32], /// Net amount for this account (positive = received, negative = sent) pub net_amount: i64, - /// Block height if confirmed, 0 if unconfirmed - pub height: u32, - /// Block hash if confirmed (32 bytes), all zeros if unconfirmed - pub block_hash: [u8; 32], - /// Unix timestamp - pub timestamp: u64, + /// Block info (zeroed if unconfirmed) + pub block_info: FFIBlockInfo, /// Fee if known, 0 if unknown pub fee: u64, /// Whether this is our transaction @@ -729,18 +725,9 @@ pub unsafe extern "C" fn managed_core_account_get_transactions( // Copy net amount ffi_record.net_amount = record.net_amount; - // Copy height (0 if unconfirmed) - ffi_record.height = record.height.unwrap_or(0); - - // Copy block hash (zeros if unconfirmed) - if let Some(block_hash) = record.block_hash { - ffi_record.block_hash = block_hash.to_byte_array(); - } else { - ffi_record.block_hash = [0u8; 32]; - } - - // Copy timestamp - ffi_record.timestamp = record.timestamp; + // Copy block info + ffi_record.block_info = + record.block_info.map(FFIBlockInfo::from).unwrap_or_else(FFIBlockInfo::empty); // Copy fee (0 if unknown) ffi_record.fee = record.fee.unwrap_or(0); diff --git a/key-wallet-ffi/src/transaction.rs b/key-wallet-ffi/src/transaction.rs index 607cc0a9d..ee424bfbe 100644 --- a/key-wallet-ffi/src/transaction.rs +++ b/key-wallet-ffi/src/transaction.rs @@ -14,7 +14,9 @@ use key_wallet::wallet::managed_wallet_info::wallet_info_interface::WalletInfoIn use secp256k1::{Message, Secp256k1, SecretKey}; use crate::error::{FFIError, FFIErrorCode}; -use crate::types::{block_info_from_ffi, FFINetwork, FFITransactionContext, FFIWallet}; +use crate::types::{ + transaction_context_from_ffi, FFIBlockInfo, FFINetwork, FFITransactionContext, FFIWallet, +}; use crate::FFIWalletManager; // MARK: - Transaction Types @@ -388,9 +390,7 @@ pub unsafe extern "C" fn wallet_check_transaction( tx_bytes: *const u8, tx_len: usize, context_type: FFITransactionContext, - block_height: u32, - block_hash: *const u8, // 32 bytes if not null - timestamp: u64, + block_info: FFIBlockInfo, update_state: bool, result_out: *mut FFITransactionCheckResult, error: *mut FFIError, @@ -419,19 +419,7 @@ pub unsafe extern "C" fn wallet_check_transaction( }; // Build the transaction context - use key_wallet::transaction_checking::TransactionContext; - let context = match context_type { - FFITransactionContext::Mempool => TransactionContext::Mempool, - FFITransactionContext::InBlock => { - let info = block_info_from_ffi(block_height, block_hash, timestamp); - TransactionContext::InBlock(info) - } - FFITransactionContext::InChainLockedBlock => { - let info = block_info_from_ffi(block_height, block_hash, timestamp); - TransactionContext::InChainLockedBlock(info) - } - FFITransactionContext::InstantSend => TransactionContext::InstantSend, - }; + let context = transaction_context_from_ffi(context_type, &block_info); // Create a ManagedWalletInfo from the wallet use key_wallet::transaction_checking::WalletTransactionChecker; diff --git a/key-wallet-ffi/src/transaction_checking.rs b/key-wallet-ffi/src/transaction_checking.rs index 17c727199..8c3cb74f2 100644 --- a/key-wallet-ffi/src/transaction_checking.rs +++ b/key-wallet-ffi/src/transaction_checking.rs @@ -10,11 +10,11 @@ use std::slice; use crate::error::{FFIError, FFIErrorCode}; use crate::managed_wallet::{managed_wallet_info_free, FFIManagedWalletInfo}; -use crate::types::{block_info_from_ffi, FFITransactionContext, FFIWallet}; +use crate::types::{transaction_context_from_ffi, FFIBlockInfo, FFITransactionContext, FFIWallet}; use dashcore::consensus::Decodable; use dashcore::Transaction; use key_wallet::transaction_checking::{ - account_checker::CoreAccountTypeMatch, TransactionContext, WalletTransactionChecker, + account_checker::CoreAccountTypeMatch, WalletTransactionChecker, }; use key_wallet::wallet::managed_wallet_info::ManagedWalletInfo; @@ -112,9 +112,7 @@ pub unsafe extern "C" fn managed_wallet_check_transaction( tx_bytes: *const u8, tx_len: usize, context_type: FFITransactionContext, - block_height: c_uint, - block_hash: *const u8, // 32 bytes if not null - timestamp: u64, + block_info: FFIBlockInfo, update_state: bool, result_out: *mut FFITransactionCheckResult, error: *mut FFIError, @@ -141,18 +139,7 @@ pub unsafe extern "C" fn managed_wallet_check_transaction( }; // Build the transaction context - let context = match context_type { - FFITransactionContext::Mempool => TransactionContext::Mempool, - FFITransactionContext::InBlock => { - let info = block_info_from_ffi(block_height, block_hash, timestamp); - TransactionContext::InBlock(info) - } - FFITransactionContext::InChainLockedBlock => { - let info = block_info_from_ffi(block_height, block_hash, timestamp); - TransactionContext::InChainLockedBlock(info) - } - FFITransactionContext::InstantSend => TransactionContext::InstantSend, - }; + let context = transaction_context_from_ffi(context_type, &block_info); // Check the transaction - wallet is now required if wallet.is_null() { diff --git a/key-wallet-ffi/src/types.rs b/key-wallet-ffi/src/types.rs index 37b16348f..fec02a89b 100644 --- a/key-wallet-ffi/src/types.rs +++ b/key-wallet-ffi/src/types.rs @@ -3,28 +3,61 @@ use dashcore::hashes::Hash; use key_wallet::transaction_checking::{BlockInfo, TransactionContext}; use key_wallet::{Network, Wallet}; -use std::os::raw::{c_char, c_uint}; +use std::os::raw::c_char; use std::sync::Arc; -/// Convert FFI block parameters to a `BlockInfo`. -/// -/// # Safety -/// -/// If `block_hash` is non-null it must point to 32 readable bytes. -pub(crate) unsafe fn block_info_from_ffi( - height: u32, - block_hash: *const u8, - timestamp: u64, -) -> BlockInfo { - let block_hash = if !block_hash.is_null() { - let hash_bytes = std::slice::from_raw_parts(block_hash, 32); - let mut arr = [0u8; 32]; - arr.copy_from_slice(hash_bytes); - dashcore::BlockHash::from_byte_array(arr) - } else { - dashcore::BlockHash::all_zeros() - }; - BlockInfo::new(height, block_hash, timestamp as u32) +/// FFI-compatible block metadata (height, hash, timestamp). +#[repr(C)] +#[derive(Debug, Clone, Copy)] +pub struct FFIBlockInfo { + /// Block height + pub height: u32, + /// Block hash (32 bytes) + pub block_hash: [u8; 32], + /// Unix timestamp + pub timestamp: u32, +} + +impl FFIBlockInfo { + /// All-zeros placeholder used for unconfirmed contexts. + pub fn empty() -> Self { + Self { + height: 0, + block_hash: [0u8; 32], + timestamp: 0, + } + } + + /// Convert to native `BlockInfo`. + pub fn to_block_info(&self) -> BlockInfo { + let block_hash = dashcore::BlockHash::from_byte_array(self.block_hash); + BlockInfo::new(self.height, block_hash, self.timestamp) + } +} + +impl From for FFIBlockInfo { + fn from(info: BlockInfo) -> Self { + Self { + height: info.height(), + block_hash: info.block_hash().to_byte_array(), + timestamp: info.timestamp(), + } + } +} + +/// Convert an `FFIBlockInfo` and context type to a native `TransactionContext`. +pub(crate) fn transaction_context_from_ffi( + context_type: FFITransactionContext, + block_info: &FFIBlockInfo, +) -> TransactionContext { + match context_type { + FFITransactionContext::Mempool => TransactionContext::Mempool, + FFITransactionContext::InstantSend => TransactionContext::InstantSend, + FFITransactionContext::InBlock => TransactionContext::InBlock(block_info.to_block_info()), + FFITransactionContext::InChainLockedBlock => { + TransactionContext::InChainLockedBlock(block_info.to_block_info()) + } + } } /// FFI Network type (single network) @@ -760,62 +793,37 @@ impl From for FFITransactionContext { pub struct FFITransactionContextDetails { /// The context type pub context_type: FFITransactionContext, - /// Block height (0 for mempool) - pub height: c_uint, - /// Block hash (32 bytes, null for mempool or if unknown) - pub block_hash: *const u8, - /// Timestamp (0 if unknown) - pub timestamp: c_uint, + /// Block info (zeroed for mempool/instant-send contexts) + pub block_info: FFIBlockInfo, } impl FFITransactionContextDetails { /// Create a mempool context pub fn mempool() -> Self { - FFITransactionContextDetails { + Self { context_type: FFITransactionContext::Mempool, - height: 0, - block_hash: std::ptr::null(), - timestamp: 0, + block_info: FFIBlockInfo::empty(), } } /// Create an in-block context - pub fn in_block(height: c_uint, block_hash: *const u8, timestamp: c_uint) -> Self { - FFITransactionContextDetails { + pub fn in_block(block_info: FFIBlockInfo) -> Self { + Self { context_type: FFITransactionContext::InBlock, - height, - block_hash, - timestamp, + block_info, } } /// Create a chain-locked block context - pub fn in_chain_locked_block(height: c_uint, block_hash: *const u8, timestamp: c_uint) -> Self { - FFITransactionContextDetails { + pub fn in_chain_locked_block(block_info: FFIBlockInfo) -> Self { + Self { context_type: FFITransactionContext::InChainLockedBlock, - height, - block_hash, - timestamp, + block_info, } } - /// Convert to the native TransactionContext + /// Convert to the native `TransactionContext` pub fn to_transaction_context(&self) -> TransactionContext { - match self.context_type { - FFITransactionContext::Mempool => TransactionContext::Mempool, - FFITransactionContext::InBlock => { - let info = unsafe { - block_info_from_ffi(self.height, self.block_hash, self.timestamp as u64) - }; - TransactionContext::InBlock(info) - } - FFITransactionContext::InChainLockedBlock => { - let info = unsafe { - block_info_from_ffi(self.height, self.block_hash, self.timestamp as u64) - }; - TransactionContext::InChainLockedBlock(info) - } - FFITransactionContext::InstantSend => TransactionContext::InstantSend, - } + transaction_context_from_ffi(self.context_type, &self.block_info) } } diff --git a/key-wallet-ffi/src/wallet_manager_tests.rs b/key-wallet-ffi/src/wallet_manager_tests.rs index 6997c237f..06312105a 100644 --- a/key-wallet-ffi/src/wallet_manager_tests.rs +++ b/key-wallet-ffi/src/wallet_manager_tests.rs @@ -627,19 +627,14 @@ mod tests { ]; // Create transaction contexts for testing - let mempool_context = crate::types::FFITransactionContextDetails { - context_type: crate::types::FFITransactionContext::Mempool, - height: 0, - block_hash: ptr::null(), - timestamp: 0, - }; + let mempool_context = crate::types::FFITransactionContextDetails::mempool(); - let block_context = crate::types::FFITransactionContextDetails { - context_type: crate::types::FFITransactionContext::InBlock, - height: 100000, - block_hash: ptr::null(), - timestamp: 1234567890, - }; + let block_context = + crate::types::FFITransactionContextDetails::in_block(crate::types::FFIBlockInfo { + height: 100000, + block_hash: [0u8; 32], + timestamp: 1234567890, + }); // Test processing a mempool transaction let processed = unsafe { @@ -672,12 +667,14 @@ mod tests { assert_eq!(unsafe { (*error).code }, FFIErrorCode::InvalidInput); // Test processing a chain-locked block transaction - let chain_locked_context = crate::types::FFITransactionContextDetails { - context_type: crate::types::FFITransactionContext::InChainLockedBlock, - height: 100000, - block_hash: ptr::null(), - timestamp: 1234567890, - }; + let chain_locked_context = + crate::types::FFITransactionContextDetails::in_chain_locked_block( + crate::types::FFIBlockInfo { + height: 100000, + block_hash: [0u8; 32], + timestamp: 1234567890, + }, + ); let processed = unsafe { wallet_manager::wallet_manager_process_transaction( manager, diff --git a/key-wallet/src/managed_account/mod.rs b/key-wallet/src/managed_account/mod.rs index 7f605e280..990b6d96e 100644 --- a/key-wallet/src/managed_account/mod.rs +++ b/key-wallet/src/managed_account/mod.rs @@ -344,13 +344,9 @@ impl ManagedCoreAccount { value: output.value, script_pubkey: output.script_pubkey.clone(), }; - let mut utxo = Utxo::new( - outpoint, - txout, - addr, - context.block_info().map(|i| i.height).unwrap_or(0), - tx.is_coin_base(), - ); + let block_height = context.block_info().map_or(0, |info| info.height); + let mut utxo = + Utxo::new(outpoint, txout, addr, block_height, tx.is_coin_base()); utxo.is_confirmed = context.confirmed(); utxo.is_instantlocked = matches!(context, TransactionContext::InstantSend); @@ -393,8 +389,7 @@ impl ManagedCoreAccount { if let Some(tx_record) = self.transactions.get_mut(&tx.txid()) { if !tx_record.is_confirmed() { if let Some(info) = context.block_info() { - tx_record.mark_confirmed(info.height, info.block_hash); - tx_record.timestamp = info.timestamp as u64; + tx_record.mark_confirmed(*info); changed = true; } } @@ -411,13 +406,10 @@ impl ManagedCoreAccount { context: TransactionContext, ) { let net_amount = account_match.received as i64 - account_match.sent as i64; - let block_info = context.block_info(); let tx_record = TransactionRecord { transaction: tx.clone(), txid: tx.txid(), - height: block_info.map(|i| i.height), - block_hash: block_info.map(|i| i.block_hash), - timestamp: block_info.map(|i| i.timestamp as u64).unwrap_or(0), + block_info: context.block_info().copied(), net_amount, fee: None, label: None, diff --git a/key-wallet/src/managed_account/transaction_record.rs b/key-wallet/src/managed_account/transaction_record.rs index 3f51b6e01..80fac43cf 100644 --- a/key-wallet/src/managed_account/transaction_record.rs +++ b/key-wallet/src/managed_account/transaction_record.rs @@ -3,9 +3,10 @@ //! This module contains the transaction record structure used to track //! transactions associated with accounts. +use crate::transaction_checking::BlockInfo; use alloc::string::String; use dashcore::blockdata::transaction::Transaction; -use dashcore::{BlockHash, Txid}; +use dashcore::Txid; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; @@ -17,12 +18,8 @@ pub struct TransactionRecord { pub transaction: Transaction, /// Transaction ID pub txid: Txid, - /// Block height (if confirmed) - pub height: Option, - /// Block hash (if confirmed) - pub block_hash: Option, - /// Timestamp - pub timestamp: u64, + /// Block info (height, hash, timestamp) if confirmed + pub block_info: Option, /// Net amount for this account pub net_amount: i64, /// Fee paid (if we created it) @@ -34,15 +31,13 @@ pub struct TransactionRecord { } impl TransactionRecord { - /// Create a new transaction record - pub fn new(transaction: Transaction, timestamp: u64, net_amount: i64, is_ours: bool) -> Self { + /// Create a new unconfirmed transaction record + pub fn new(transaction: Transaction, net_amount: i64, is_ours: bool) -> Self { let txid = transaction.txid(); Self { transaction, txid, - height: None, - block_hash: None, - timestamp, + block_info: None, net_amount, fee: None, label: None, @@ -53,9 +48,7 @@ impl TransactionRecord { /// Create a confirmed transaction record pub fn new_confirmed( transaction: Transaction, - height: u32, - block_hash: BlockHash, - timestamp: u64, + block_info: BlockInfo, net_amount: i64, is_ours: bool, ) -> Self { @@ -63,9 +56,7 @@ impl TransactionRecord { Self { transaction, txid, - height: Some(height), - block_hash: Some(block_hash), - timestamp, + block_info: Some(block_info), net_amount, fee: None, label: None, @@ -75,18 +66,15 @@ impl TransactionRecord { /// Calculate the number of confirmations based on current chain height pub fn confirmations(&self, current_height: u32) -> u32 { - match self.height { - Some(tx_height) if current_height >= tx_height => { - // Add 1 because the block itself counts as 1 confirmation - (current_height - tx_height) + 1 - } - _ => 0, // Unconfirmed or invalid height + match self.block_info { + Some(ref info) if current_height >= info.height => (current_height - info.height) + 1, + _ => 0, } } /// Check if the transaction is confirmed (has at least 1 confirmation) pub fn is_confirmed(&self) -> bool { - self.height.is_some() + self.block_info.is_some() } /// Check if the transaction has at least the specified number of confirmations @@ -94,6 +82,11 @@ impl TransactionRecord { self.confirmations(current_height) >= required } + /// Block height if confirmed + pub fn height(&self) -> Option { + self.block_info.as_ref().map(|info| info.height) + } + /// Set the fee for this transaction pub fn set_fee(&mut self, fee: u64) { self.fee = Some(fee); @@ -105,15 +98,13 @@ impl TransactionRecord { } /// Mark transaction as confirmed - pub fn mark_confirmed(&mut self, height: u32, block_hash: BlockHash) { - self.height = Some(height); - self.block_hash = Some(block_hash); + pub fn mark_confirmed(&mut self, block_info: BlockInfo) { + self.block_info = Some(block_info); } /// Mark transaction as unconfirmed (e.g., due to reorg) pub fn mark_unconfirmed(&mut self) { - self.height = None; - self.block_hash = None; + self.block_info = None; } /// Check if this is an incoming transaction (positive net amount) @@ -136,14 +127,18 @@ impl TransactionRecord { mod tests { use super::*; use dashcore::hashes::Hash; + use dashcore::BlockHash; + + fn test_block_info(height: u32) -> BlockInfo { + BlockInfo::new(height, BlockHash::all_zeros(), 1234567890) + } #[test] fn test_transaction_record_creation() { let tx = Transaction::dummy_empty(); - let record = TransactionRecord::new(tx.clone(), 1234567890, 50000, true); + let record = TransactionRecord::new(tx.clone(), 50000, true); assert_eq!(record.txid, tx.txid()); - assert_eq!(record.timestamp, 1234567890); assert_eq!(record.net_amount, 50000); assert!(record.is_ours); assert!(!record.is_confirmed()); @@ -152,14 +147,14 @@ mod tests { #[test] fn test_confirmations_calculation() { let tx = Transaction::dummy_empty(); - let mut record = TransactionRecord::new(tx, 1234567890, 50000, true); + let mut record = TransactionRecord::new(tx, 50000, true); // Unconfirmed transaction assert_eq!(record.confirmations(100), 0); assert!(!record.is_confirmed()); // Mark as confirmed at height 95 - record.mark_confirmed(95, BlockHash::all_zeros()); + record.mark_confirmed(test_block_info(95)); assert!(record.is_confirmed()); // At height 100, should have 6 confirmations (100 - 95 + 1) @@ -178,12 +173,12 @@ mod tests { fn test_incoming_outgoing() { let tx = Transaction::dummy_empty(); - let incoming = TransactionRecord::new(tx.clone(), 1234567890, 50000, false); + let incoming = TransactionRecord::new(tx.clone(), 50000, false); assert!(incoming.is_incoming()); assert!(!incoming.is_outgoing()); assert_eq!(incoming.amount(), 50000); - let outgoing = TransactionRecord::new(tx.clone(), 1234567890, -50000, true); + let outgoing = TransactionRecord::new(tx.clone(), -50000, true); assert!(!outgoing.is_incoming()); assert!(outgoing.is_outgoing()); assert_eq!(outgoing.amount(), 50000); @@ -191,36 +186,31 @@ mod tests { #[test] fn test_confirmed_transaction_creation() { + let info = test_block_info(100); let tx = Transaction::dummy_empty(); - let block_hash = BlockHash::all_zeros(); - let record = - TransactionRecord::new_confirmed(tx.clone(), 100, block_hash, 1234567890, 50000, true); + let record = TransactionRecord::new_confirmed(tx.clone(), info, 50000, true); - assert_eq!(record.height, Some(100)); - assert_eq!(record.block_hash, Some(block_hash)); + assert_eq!(record.height(), Some(100)); assert!(record.is_confirmed()); } #[test] fn test_mark_unconfirmed() { let tx = Transaction::dummy_empty(); - let block_hash = BlockHash::all_zeros(); - let mut record = - TransactionRecord::new_confirmed(tx, 100, block_hash, 1234567890, 50000, true); + let mut record = TransactionRecord::new_confirmed(tx, test_block_info(100), 50000, true); assert!(record.is_confirmed()); // Simulate reorg record.mark_unconfirmed(); assert!(!record.is_confirmed()); - assert_eq!(record.height, None); - assert_eq!(record.block_hash, None); + assert_eq!(record.block_info, None); } #[test] fn test_labels_and_fees() { let tx = Transaction::dummy_empty(); - let mut record = TransactionRecord::new(tx, 1234567890, -50000, true); + let mut record = TransactionRecord::new(tx, -50000, true); assert_eq!(record.fee, None); assert_eq!(record.label, None); diff --git a/key-wallet/src/tests/spent_outpoints_tests.rs b/key-wallet/src/tests/spent_outpoints_tests.rs index a9e941b26..4d0aa3ab9 100644 --- a/key-wallet/src/tests/spent_outpoints_tests.rs +++ b/key-wallet/src/tests/spent_outpoints_tests.rs @@ -35,7 +35,7 @@ fn receive_only_tx() -> Transaction { } fn record_from_tx(tx: &Transaction) -> TransactionRecord { - TransactionRecord::new(tx.clone(), 0, 0, false) + TransactionRecord::new(tx.clone(), 0, false) } #[test] diff --git a/key-wallet/src/transaction_checking/transaction_router/tests/helpers.rs b/key-wallet/src/transaction_checking/transaction_router/tests/helpers.rs index 6cbc4b025..0c97453d4 100644 --- a/key-wallet/src/transaction_checking/transaction_router/tests/helpers.rs +++ b/key-wallet/src/transaction_checking/transaction_router/tests/helpers.rs @@ -1,9 +1,16 @@ //! Helper functions for transaction router tests +use crate::transaction_checking::BlockInfo; use dashcore::blockdata::transaction::special_transaction::asset_lock::AssetLockPayload; use dashcore::blockdata::transaction::special_transaction::TransactionPayload; use dashcore::blockdata::transaction::Transaction; -use dashcore::{Address, Network, TxOut}; +use dashcore::hashes::Hash; +use dashcore::{Address, BlockHash, Network, TxOut}; + +/// Creates a `BlockInfo` with the given height and deterministic defaults. +pub fn test_block_info(height: u32) -> BlockInfo { + BlockInfo::new(height, BlockHash::all_zeros(), 1234567890) +} /// Returns a deterministic test address for creating dummy transactions. pub fn test_addr() -> Address { diff --git a/key-wallet/src/transaction_checking/transaction_router/tests/identity_transactions.rs b/key-wallet/src/transaction_checking/transaction_router/tests/identity_transactions.rs index 0ffb269ae..adcbd8717 100644 --- a/key-wallet/src/transaction_checking/transaction_router/tests/identity_transactions.rs +++ b/key-wallet/src/transaction_checking/transaction_router/tests/identity_transactions.rs @@ -5,7 +5,7 @@ use crate::account::AccountType; use crate::transaction_checking::transaction_router::{ AccountTypeToCheck, TransactionRouter, TransactionType, }; -use crate::transaction_checking::{BlockInfo, TransactionContext, WalletTransactionChecker}; +use crate::transaction_checking::{TransactionContext, WalletTransactionChecker}; use crate::wallet::initialization::WalletAccountCreationOptions; use crate::wallet::{ManagedWalletInfo, Wallet}; use crate::Network; @@ -14,7 +14,7 @@ use dashcore::blockdata::transaction::special_transaction::asset_lock::AssetLock use dashcore::blockdata::transaction::special_transaction::TransactionPayload; use dashcore::blockdata::transaction::Transaction; use dashcore::hashes::Hash; -use dashcore::{BlockHash, OutPoint, TxIn, TxOut, Txid}; +use dashcore::{OutPoint, TxIn, TxOut, Txid}; #[test] fn test_identity_registration() { @@ -114,11 +114,7 @@ async fn test_identity_registration_account_routing() { )), }; - let context = TransactionContext::InBlock(BlockInfo::new( - 100000, - BlockHash::from_slice(&[0u8; 32]).expect("Failed to create block hash from bytes"), - 1234567890, - )); + let context = TransactionContext::InBlock(test_block_info(100000)); // First check without updating state let result = @@ -190,11 +186,7 @@ async fn test_normal_payment_to_identity_address_not_detected() { script_pubkey: address.script_pubkey(), }); - let context = TransactionContext::InBlock(BlockInfo::new( - 100000, - BlockHash::from_slice(&[0u8; 32]).expect("Failed to create block hash from bytes"), - 1234567890, - )); + let context = TransactionContext::InBlock(test_block_info(100000)); let result = managed_wallet_info .check_core_transaction( diff --git a/key-wallet/src/transaction_checking/transaction_router/tests/routing.rs b/key-wallet/src/transaction_checking/transaction_router/tests/routing.rs index 3dba41bee..ac9aabda9 100644 --- a/key-wallet/src/transaction_checking/transaction_router/tests/routing.rs +++ b/key-wallet/src/transaction_checking/transaction_router/tests/routing.rs @@ -1,6 +1,6 @@ //! Tests for transaction routing logic -use super::helpers::test_addr; +use super::helpers::{test_addr, test_block_info}; use crate::account::{AccountType, StandardAccountType}; use crate::managed_account::address_pool::KeySource; use crate::managed_account::managed_account_type::ManagedAccountType; @@ -8,13 +8,12 @@ use crate::test_utils::TestWalletContext; use crate::transaction_checking::transaction_router::{ AccountTypeToCheck, TransactionRouter, TransactionType, }; -use crate::transaction_checking::{BlockInfo, TransactionContext, WalletTransactionChecker}; +use crate::transaction_checking::{TransactionContext, WalletTransactionChecker}; use crate::wallet::initialization::WalletAccountCreationOptions; use crate::wallet::{ManagedWalletInfo, Wallet}; use crate::Network; use dashcore::blockdata::transaction::Transaction; -use dashcore::hashes::Hash; -use dashcore::{BlockHash, ScriptBuf, TxOut}; +use dashcore::{ScriptBuf, TxOut}; #[test] fn test_standard_transaction_routing() { @@ -45,11 +44,7 @@ async fn test_transaction_routing_to_bip44_account() { }); // Check the transaction using the wallet's managed info - let context = TransactionContext::InBlock(BlockInfo::new( - 100000, - BlockHash::from_slice(&[0u8; 32]).expect("Failed to create block hash from bytes"), - 1234567890, - )); + let context = TransactionContext::InBlock(test_block_info(100000)); // Check the transaction using the managed wallet info let result = managed_wallet_info @@ -113,11 +108,7 @@ async fn test_transaction_routing_to_bip32_account() { }); // Check the transaction using the managed wallet info - let context = TransactionContext::InBlock(BlockInfo::new( - 100000, - BlockHash::from_slice(&[0u8; 32]).expect("Failed to create block hash from bytes"), - 1234567890, - )); + let context = TransactionContext::InBlock(test_block_info(100000)); // Check with update_state = false let result = @@ -233,11 +224,7 @@ async fn test_transaction_routing_to_coinjoin_account() { script_pubkey: ScriptBuf::new(), }); - let context = TransactionContext::InBlock(BlockInfo::new( - 100000, - BlockHash::from_slice(&[0u8; 32]).expect("Failed to create block hash from bytes"), - 1234567890, - )); + let context = TransactionContext::InBlock(test_block_info(100000)); let result = managed_wallet_info.check_core_transaction(&tx, context, &mut wallet, true, true).await; @@ -334,11 +321,7 @@ async fn test_transaction_affects_multiple_accounts() { script_pubkey: address2.script_pubkey(), }); - let context = TransactionContext::InBlock(BlockInfo::new( - 100000, - BlockHash::from_slice(&[0u8; 32]).expect("Failed to create block hash from bytes"), - 1234567890, - )); + let context = TransactionContext::InBlock(test_block_info(100000)); // Check the transaction let result = managed_wallet_info diff --git a/key-wallet/src/transaction_checking/wallet_checker.rs b/key-wallet/src/transaction_checking/wallet_checker.rs index f02e9b5c4..bb9adcdc9 100644 --- a/key-wallet/src/transaction_checking/wallet_checker.rs +++ b/key-wallet/src/transaction_checking/wallet_checker.rs @@ -628,9 +628,7 @@ mod tests { let stored_tx = managed_account.transactions.get(&tx.txid()).expect("Should have stored transaction"); - assert_eq!(stored_tx.height, None, "Mempool transaction should have no height"); - assert_eq!(stored_tx.block_hash, None, "Mempool transaction should have no block hash"); - assert_eq!(stored_tx.timestamp, 0, "Mempool transaction should have timestamp 0"); + assert_eq!(stored_tx.block_info, None, "Mempool transaction should have no block info"); } /// Test that rescanning a block marks transactions as existing @@ -823,7 +821,7 @@ mod tests { // Verify unconfirmed state assert!(!ctx.transaction(&txid).is_confirmed(), "Mempool tx should be unconfirmed"); - assert_eq!(ctx.transaction(&txid).height, None); + assert_eq!(ctx.transaction(&txid).block_info, None); assert!(!ctx.first_utxo().is_confirmed, "Mempool UTXO should be unconfirmed"); let total_tx_before = ctx.managed_wallet.metadata.total_transactions; @@ -840,9 +838,9 @@ mod tests { // Verify confirmed state let record = ctx.transaction(&txid); assert!(record.is_confirmed(), "Tx should now be confirmed"); - assert_eq!(record.height, Some(500)); - assert_eq!(record.block_hash, Some(block_hash)); - assert_eq!(record.timestamp, 1700000000); + assert_eq!(record.height(), Some(500)); + assert_eq!(record.block_info.unwrap().block_hash, block_hash); + assert_eq!(record.block_info.unwrap().timestamp, 1700000000); assert!(ctx.first_utxo().is_confirmed, "UTXO should now be confirmed"); assert_eq!( @@ -886,7 +884,7 @@ mod tests { let result = ctx.check_transaction(&tx, block_context).await; assert!(!result.is_new_transaction); assert!(ctx.transaction(&txid).is_confirmed()); - assert_eq!(ctx.transaction(&txid).height, Some(1000)); + assert_eq!(ctx.transaction(&txid).height(), Some(1000)); assert!(ctx.first_utxo().is_confirmed); assert_eq!(ctx.managed_wallet.balance().spendable(), 200_000); @@ -971,9 +969,9 @@ mod tests { let record = ctx.transaction(&txid); assert!(record.is_confirmed()); - assert_eq!(record.height, Some(800)); - assert_eq!(record.block_hash, Some(block_hash)); - assert_eq!(record.timestamp, 1700000000); + assert_eq!(record.height(), Some(800)); + assert_eq!(record.block_info.unwrap().block_hash, block_hash); + assert_eq!(record.block_info.unwrap().timestamp, 1700000000); assert!(ctx.first_utxo().is_confirmed); } @@ -1010,9 +1008,9 @@ mod tests { // Verify the transaction was recorded with block context let record = account.transactions.get(&txid).expect("Should have backfilled record"); assert!(record.is_confirmed()); - assert_eq!(record.height, Some(600)); - assert_eq!(record.block_hash, Some(block_hash)); - assert_eq!(record.timestamp, 1700000000); + assert_eq!(record.height(), Some(600)); + assert_eq!(record.block_info.unwrap().block_hash, block_hash); + assert_eq!(record.block_info.unwrap().timestamp, 1700000000); assert_eq!(record.net_amount, 250_000); // Verify UTXO was also created @@ -1059,7 +1057,7 @@ mod tests { let record = account.transactions.get(&txid).expect("Should have record"); assert!(record.is_confirmed()); - assert_eq!(record.height, Some(700)); - assert_eq!(record.block_hash, Some(block_hash)); + assert_eq!(record.height(), Some(700)); + assert_eq!(record.block_info.unwrap().block_hash, block_hash); } }