diff --git a/dash-spv/tests/dashd_sync/setup.rs b/dash-spv/tests/dashd_sync/setup.rs index 002663a5b..1f30232d0 100644 --- a/dash-spv/tests/dashd_sync/setup.rs +++ b/dash-spv/tests/dashd_sync/setup.rs @@ -168,7 +168,7 @@ impl TestContext { .all_accounts() .iter() .any(|account| account.transactions.contains_key(txid)) - || wallet_info.immature_transactions().iter().any(|tx| &tx.txid() == txid) + || wallet_info.immature_txids().contains(txid) } /// Validate that the context wallet matches the expected baseline from dashd. @@ -200,8 +200,8 @@ impl TestContext { spv_txids.insert(txid.to_string()); } } - for tx in wallet_info.immature_transactions() { - spv_txids.insert(tx.txid().to_string()); + for txid in wallet_info.immature_txids() { + spv_txids.insert(txid.to_string()); } let expected_txids: HashSet = self @@ -309,7 +309,7 @@ pub(super) async fn client_has_transaction( .all_accounts() .iter() .any(|account| account.transactions.contains_key(txid)) - || wallet_info.immature_transactions().iter().any(|tx| &tx.txid() == txid) + || wallet_info.immature_txids().contains(txid) } /// Creates a new SPV client and starts it with a `TestEventHandler`. diff --git a/key-wallet/src/managed_account/mod.rs b/key-wallet/src/managed_account/mod.rs index b6a55316a..6c471d2af 100644 --- a/key-wallet/src/managed_account/mod.rs +++ b/key-wallet/src/managed_account/mod.rs @@ -1200,6 +1200,8 @@ impl<'de> Deserialize<'de> for ManagedCoreAccount { let helper = Helper::deserialize(deserializer)?; + // Rebuild from all transaction inputs (not just `input_details`) to match + // runtime behavior, which tracks every input of every recorded transaction. let spent_outpoints = helper .transactions .values() diff --git a/key-wallet/src/transaction_checking/wallet_checker.rs b/key-wallet/src/transaction_checking/wallet_checker.rs index de4c51851..9f50b34ea 100644 --- a/key-wallet/src/transaction_checking/wallet_checker.rs +++ b/key-wallet/src/transaction_checking/wallet_checker.rs @@ -411,10 +411,10 @@ mod tests { let utxo = managed_account.utxos.values().next().expect("Should have UTXO"); assert!(utxo.is_coinbase, "UTXO should be marked as coinbase"); - // Coinbase should be in immature_transactions() since it hasn't matured - let immature_txs = managed_wallet.immature_transactions(); + // Coinbase should be in immature_txids() since it hasn't matured + let immature_txs = managed_wallet.immature_txids(); assert_eq!(immature_txs.len(), 1, "Should have one immature transaction"); - assert_eq!(immature_txs[0].txid(), coinbase_tx.txid()); + assert!(immature_txs.contains(&coinbase_tx.txid())); // Immature balance should reflect the coinbase value assert_eq!(managed_wallet.balance().immature(), 5_000_000_000); @@ -566,8 +566,8 @@ mod tests { assert!(utxo.is_coinbase, "UTXO should be marked as coinbase"); assert_eq!(utxo.height, block_height); - // Coinbase is in immature_transactions() since it hasn't matured - let immature_txs = managed_wallet.immature_transactions(); + // Coinbase is in immature_txids() since it hasn't matured + let immature_txs = managed_wallet.immature_txids(); assert_eq!(immature_txs.len(), 1, "Should have one immature transaction"); // Immature balance should reflect the coinbase value @@ -596,8 +596,8 @@ mod tests { "Coinbase should still be in regular transactions" ); - // Coinbase is no longer in immature_transactions() - let immature_txs = managed_wallet.immature_transactions(); + // Coinbase is no longer in immature_txids() + let immature_txs = managed_wallet.immature_txids(); assert!(immature_txs.is_empty(), "Matured coinbase should not be in immature transactions"); // Immature balance should now be zero diff --git a/key-wallet/src/wallet/managed_wallet_info/wallet_info_interface.rs b/key-wallet/src/wallet/managed_wallet_info/wallet_info_interface.rs index b4cf407d3..40d111b92 100644 --- a/key-wallet/src/wallet/managed_wallet_info/wallet_info_interface.rs +++ b/key-wallet/src/wallet/managed_wallet_info/wallet_info_interface.rs @@ -12,7 +12,7 @@ use crate::{Network, Utxo, Wallet, WalletCoreBalance}; use alloc::collections::BTreeSet; use alloc::vec::Vec; use dashcore::prelude::CoreBlockHeight; -use dashcore::{Address as DashAddress, Transaction, Txid}; +use dashcore::{Address as DashAddress, Txid}; /// Trait that wallet info types must implement to work with WalletManager pub trait WalletInfoInterface: Sized + WalletTransactionChecker + ManagedAccountOperations { @@ -81,8 +81,8 @@ pub trait WalletInfoInterface: Sized + WalletTransactionChecker + ManagedAccount /// Get accounts (immutable) fn accounts(&self) -> &ManagedAccountCollection; - /// Get immature transactions - fn immature_transactions(&self) -> Vec; + /// Get immature coinbase transaction IDs + fn immature_txids(&self) -> BTreeSet; /// Return the last fully processed height of the wallet. fn synced_height(&self) -> CoreBlockHeight; @@ -209,10 +209,8 @@ impl WalletInfoInterface for ManagedWalletInfo { &self.accounts } - fn immature_transactions(&self) -> Vec { - let mut immature_txids: BTreeSet = BTreeSet::new(); - - // Find txids of immature coinbase UTXOs + fn immature_txids(&self) -> BTreeSet { + let mut immature_txids = BTreeSet::new(); for account in self.accounts.all_accounts() { for utxo in account.utxos.values() { if utxo.is_coinbase && !utxo.is_mature(self.synced_height()) { @@ -220,17 +218,7 @@ impl WalletInfoInterface for ManagedWalletInfo { } } } - - // Get the actual transactions - let mut transactions = Vec::new(); - for account in self.accounts.all_accounts() { - for (txid, record) in &account.transactions { - if immature_txids.contains(txid) { - transactions.push(record.transaction.clone()); - } - } - } - transactions + immature_txids } fn update_synced_height(&mut self, current_height: u32) { diff --git a/key-wallet/tests/spv_integration_tests.rs b/key-wallet/tests/spv_integration_tests.rs index d3fd99655..d87bcd689 100644 --- a/key-wallet/tests/spv_integration_tests.rs +++ b/key-wallet/tests/spv_integration_tests.rs @@ -138,7 +138,7 @@ async fn test_immature_balance_matures_during_block_processing() { // Verify the coinbase is detected and stored as immature let wallet_info = manager.get_wallet_info(&wallet_id).expect("Wallet info should exist"); assert!( - wallet_info.immature_transactions().contains(&coinbase_tx), + wallet_info.immature_txids().contains(&coinbase_tx.txid()), "Coinbase should be in immature transactions" ); assert_eq!( @@ -158,7 +158,7 @@ async fn test_immature_balance_matures_during_block_processing() { // Verify still immature just before maturity let wallet_info = manager.get_wallet_info(&wallet_id).expect("Wallet info should exist"); assert!( - wallet_info.immature_transactions().contains(&coinbase_tx), + wallet_info.immature_txids().contains(&coinbase_tx.txid()), "Coinbase should still be immature at height {}", maturity_height - 1 ); @@ -170,7 +170,7 @@ async fn test_immature_balance_matures_during_block_processing() { // Verify the coinbase has matured let wallet_info = manager.get_wallet_info(&wallet_id).expect("Wallet info should exist"); assert!( - !wallet_info.immature_transactions().contains(&coinbase_tx), + !wallet_info.immature_txids().contains(&coinbase_tx.txid()), "Coinbase should no longer be immature after maturity height" ); assert_eq!(