From 951b79026b2488406e41eb2225105f107cc1a0fa Mon Sep 17 00:00:00 2001 From: PastaClaw Date: Thu, 16 Jul 2026 08:30:22 -0500 Subject: [PATCH 1/4] fix(key-wallet): route Coinbase/AssetUnlock to all fund-bearing accounts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Coinbase and AssetUnlock outputs can pay any user-chosen address, including CoinJoin and DashPay. get_relevant_account_types previously returned only StandardBIP44/BIP32 for those classifications, so a match on those addresses was dropped after the block download — the credit-side mirror of the #867 AssetLock debit bug. Use fund_bearing_account_types() for both arms (membership-based discovery, like Dash Core's IsMine). Update routing unit tests to expect all five fund-bearing types. Closes #900 --- .../transaction_router/mod.rs | 17 ++++++++++------- .../transaction_router/tests/asset_unlock.rs | 13 ++++++++++--- .../transaction_router/tests/coinbase.rs | 11 +++++++---- 3 files changed, 27 insertions(+), 14 deletions(-) diff --git a/key-wallet/src/transaction_checking/transaction_router/mod.rs b/key-wallet/src/transaction_checking/transaction_router/mod.rs index 51f35acbe..d974ac3a3 100644 --- a/key-wallet/src/transaction_checking/transaction_router/mod.rs +++ b/key-wallet/src/transaction_checking/transaction_router/mod.rs @@ -156,14 +156,17 @@ impl TransactionRouter { ]); accounts } - TransactionType::AssetUnlock => { - vec![AccountTypeToCheck::StandardBIP44, AccountTypeToCheck::StandardBIP32] + // Credit-side mirror of the AssetLock debit fix (#867 / #900): a coinbase + // (mining reward / masternode payout) or asset unlock (Platform credit + // withdrawal) can pay any user-chosen address, including CoinJoin and + // DashPay. Only the account types returned here are consulted for + // ownership, so omitting those accounts dropped the coin after the + // block was already downloaded. Discovery is membership-based like + // Dash Core's `IsMine`, so consulting the full fund-bearing set never + // yields false positives. + TransactionType::AssetUnlock | TransactionType::Coinbase => { + Self::fund_bearing_account_types() } - TransactionType::Coinbase => vec![ - // Check all account types for unknown special transactions - AccountTypeToCheck::StandardBIP44, - AccountTypeToCheck::StandardBIP32, - ], TransactionType::Ignored => vec![], } } diff --git a/key-wallet/src/transaction_checking/transaction_router/tests/asset_unlock.rs b/key-wallet/src/transaction_checking/transaction_router/tests/asset_unlock.rs index 6371710b1..9b911342f 100644 --- a/key-wallet/src/transaction_checking/transaction_router/tests/asset_unlock.rs +++ b/key-wallet/src/transaction_checking/transaction_router/tests/asset_unlock.rs @@ -21,10 +21,14 @@ fn test_asset_unlock_routing() { let tx_type = TransactionType::AssetUnlock; let accounts = TransactionRouter::get_relevant_account_types(&tx_type); - // Asset unlock only goes to standard accounts - assert_eq!(accounts.len(), 2); + // Asset unlock withdrawals can pay any fund-bearing address (destination is + // user-chosen), so routing must cover the full fund-bearing set. + assert_eq!(accounts.len(), 5, "AssetUnlock should route to all fund-bearing account types"); assert!(accounts.contains(&AccountTypeToCheck::StandardBIP44)); assert!(accounts.contains(&AccountTypeToCheck::StandardBIP32)); + assert!(accounts.contains(&AccountTypeToCheck::CoinJoin)); + assert!(accounts.contains(&AccountTypeToCheck::DashpayReceivingFunds)); + assert!(accounts.contains(&AccountTypeToCheck::DashpayExternalAccount)); // Should NOT check identity accounts - those are for locks only assert!(!accounts.contains(&AccountTypeToCheck::IdentityRegistration)); @@ -62,9 +66,12 @@ fn test_asset_unlock_classification() { // Verify routing for AssetUnlock let accounts = TransactionRouter::get_relevant_account_types(&tx_type); - assert_eq!(accounts.len(), 2, "AssetUnlock should route to exactly 2 account types"); + assert_eq!(accounts.len(), 5, "AssetUnlock should route to all fund-bearing account types"); assert!(accounts.contains(&AccountTypeToCheck::StandardBIP44)); assert!(accounts.contains(&AccountTypeToCheck::StandardBIP32)); + assert!(accounts.contains(&AccountTypeToCheck::CoinJoin)); + assert!(accounts.contains(&AccountTypeToCheck::DashpayReceivingFunds)); + assert!(accounts.contains(&AccountTypeToCheck::DashpayExternalAccount)); } #[tokio::test] diff --git a/key-wallet/src/transaction_checking/transaction_router/tests/coinbase.rs b/key-wallet/src/transaction_checking/transaction_router/tests/coinbase.rs index 0ed906a49..d543a061d 100644 --- a/key-wallet/src/transaction_checking/transaction_router/tests/coinbase.rs +++ b/key-wallet/src/transaction_checking/transaction_router/tests/coinbase.rs @@ -273,13 +273,16 @@ fn test_coinbase_routing() { let tx_type = TransactionType::Coinbase; let accounts = TransactionRouter::get_relevant_account_types(&tx_type); - // Coinbase should route to standard accounts - assert_eq!(accounts.len(), 2, "Coinbase should route to exactly 2 account types"); + // Coinbase can pay any fund-bearing address (mining reward / masternode + // payout is user-chosen), so routing must cover the full fund-bearing set. + assert_eq!(accounts.len(), 5, "Coinbase should route to all fund-bearing account types"); assert!(accounts.contains(&AccountTypeToCheck::StandardBIP44)); assert!(accounts.contains(&AccountTypeToCheck::StandardBIP32)); + assert!(accounts.contains(&AccountTypeToCheck::CoinJoin)); + assert!(accounts.contains(&AccountTypeToCheck::DashpayReceivingFunds)); + assert!(accounts.contains(&AccountTypeToCheck::DashpayExternalAccount)); - // Should NOT route to special account types - assert!(!accounts.contains(&AccountTypeToCheck::CoinJoin)); + // Should NOT route to non-fund-bearing special account types assert!(!accounts.contains(&AccountTypeToCheck::IdentityRegistration)); assert!(!accounts.contains(&AccountTypeToCheck::ProviderOwnerKeys)); } From d66cd9655265b945fbbb21ff8e0fe3c4ac96cf0c Mon Sep 17 00:00:00 2001 From: PastaClaw Date: Thu, 16 Jul 2026 08:32:10 -0500 Subject: [PATCH 2/4] test(key-wallet): credit CoinJoin for coinbase and asset-unlock outputs End-to-end regressions for #900: a coinbase mining reward and an AssetUnlock Platform withdrawal that pay a CoinJoin address must be discovered, create a UTXO on that account, and credit the wallet balance. Mirrors the #867 AssetLock debit regression shape; fails on pre-fix routing where only StandardBIP44/BIP32 were consulted for those classifications. --- .../transaction_checking/wallet_checker.rs | 254 ++++++++++++++++++ 1 file changed, 254 insertions(+) diff --git a/key-wallet/src/transaction_checking/wallet_checker.rs b/key-wallet/src/transaction_checking/wallet_checker.rs index 7ea653924..bc48c52f1 100644 --- a/key-wallet/src/transaction_checking/wallet_checker.rs +++ b/key-wallet/src/transaction_checking/wallet_checker.rs @@ -744,6 +744,260 @@ mod tests { ); } + /// Regression: a coinbase that pays a CoinJoin address must credit that account. + /// + /// `check_core_transaction` only consults the account types returned by + /// `TransactionRouter::get_relevant_account_types`. Before the fix, the + /// `Coinbase` arm returned only StandardBIP44/BIP32, so a mining reward or + /// masternode payout to a CoinJoin (or DashPay) address was never matched: + /// the block was still downloaded (filters query all scripts), but the + /// output was dropped purely by the account-type narrowing, undercounting + /// the balance (dashpay/rust-dashcore#900). Discovery is membership-based + /// like Dash Core's `IsMine`, so consulting the full fund-bearing set cannot + /// yield a false positive. + #[tokio::test] + async fn test_coinbase_paying_coinjoin_address_is_credited() { + use crate::managed_account::managed_account_type::ManagedAccountType; + use crate::transaction_checking::transaction_router::TransactionRouter; + + let network = Network::Testnet; + + let mut wallet = Wallet::new_random(network, WalletAccountCreationOptions::None) + .expect("Should create wallet"); + wallet + .add_account( + AccountType::CoinJoin { + index: 0, + }, + None, + ) + .expect("Should add CoinJoin account"); + + let mut managed_wallet = + ManagedWalletInfo::from_wallet_with_name(&wallet, "Test".to_string(), 0); + + let coinjoin_xpub = + wallet.accounts.coinjoin_accounts.get(&0).expect("coinjoin account").account_xpub; + let coinjoin_address = { + let managed_account = + managed_wallet.first_coinjoin_managed_account_mut().expect("managed coinjoin"); + if let ManagedAccountType::CoinJoin { + external_addresses, + .. + } = managed_account.managed_account_type_mut() + { + external_addresses + .next_unused(&KeySource::Public(coinjoin_xpub), true) + .expect("coinjoin address") + } else { + panic!("Expected CoinJoin account type"); + } + }; + + let reward = 5_000_000_000u64; + let coinbase_tx = Transaction { + version: 2, + lock_time: 0, + input: vec![TxIn { + previous_output: OutPoint { + txid: Txid::all_zeros(), + vout: 0xffffffff, + }, + script_sig: ScriptBuf::new(), + sequence: 0xffffffff, + witness: dashcore::Witness::new(), + }], + output: vec![TxOut { + value: reward, + script_pubkey: coinjoin_address.script_pubkey(), + }], + special_transaction_payload: None, + }; + assert_eq!( + TransactionRouter::classify_transaction(&coinbase_tx), + TransactionType::Coinbase, + "tx must classify as Coinbase so it routes through the Coinbase arm" + ); + + let block_height = 100_000; + let context = TransactionContext::InBlock(BlockInfo::new( + block_height, + BlockHash::from_slice(&[9u8; 32]).expect("Should create block hash"), + 1_650_000_200, + )); + let result = managed_wallet + .check_core_transaction(&coinbase_tx, context, &mut wallet, true, true) + .await; + managed_wallet.update_last_processed_height(block_height); + + assert!(result.is_relevant, "coinbase paying a CoinJoin address must be relevant"); + assert_eq!( + result.total_received, reward, + "coinbase must credit the CoinJoin output value" + ); + + let coinjoin_account = + managed_wallet.first_coinjoin_managed_account().expect("coinjoin account"); + assert!( + coinjoin_account.transactions().contains_key(&coinbase_tx.txid()), + "coinbase must be recorded on the CoinJoin account" + ); + assert_eq!( + coinjoin_account.utxos.len(), + 1, + "coinbase must create a CoinJoin UTXO" + ); + let utxo = coinjoin_account.utxos.values().next().expect("CoinJoin UTXO"); + assert!(utxo.is_coinbase, "credited UTXO must be marked coinbase"); + + // Aggregate wallet balance — the actual regression is a lost credit. + // Before the fix, routing never consulted CoinJoin, so the reward was + // dropped and immature balance stayed 0. + assert_eq!( + managed_wallet.balance.immature(), + reward, + "immature balance must credit the CoinJoin coinbase reward" + ); + assert_eq!( + managed_wallet.balance.total(), + reward, + "total balance must include the immature CoinJoin coinbase" + ); + } + + /// Sibling credit-side regression for AssetUnlock (Platform credit withdrawal). + /// + /// Same membership-based routing gap as the coinbase case above: before the + /// fix, `AssetUnlock` only consulted StandardBIP44/BIP32, so a withdrawal to + /// a CoinJoin address was never credited (dashpay/rust-dashcore#900). + #[tokio::test] + async fn test_asset_unlock_paying_coinjoin_address_is_credited() { + use crate::managed_account::managed_account_type::ManagedAccountType; + use crate::transaction_checking::transaction_router::TransactionRouter; + use dashcore::blockdata::transaction::special_transaction::asset_unlock::qualified_asset_unlock::AssetUnlockPayload; + use dashcore::blockdata::transaction::special_transaction::asset_unlock::request_info::AssetUnlockRequestInfo; + use dashcore::blockdata::transaction::special_transaction::asset_unlock::unqualified_asset_unlock::AssetUnlockBasePayload; + use dashcore::blockdata::transaction::special_transaction::TransactionPayload; + use dashcore::bls_sig_utils::BLSSignature; + + let network = Network::Testnet; + + let mut wallet = Wallet::new_random(network, WalletAccountCreationOptions::None) + .expect("Should create wallet"); + wallet + .add_account( + AccountType::CoinJoin { + index: 0, + }, + None, + ) + .expect("Should add CoinJoin account"); + + let mut managed_wallet = + ManagedWalletInfo::from_wallet_with_name(&wallet, "Test".to_string(), 0); + + let coinjoin_xpub = + wallet.accounts.coinjoin_accounts.get(&0).expect("coinjoin account").account_xpub; + let coinjoin_address = { + let managed_account = + managed_wallet.first_coinjoin_managed_account_mut().expect("managed coinjoin"); + if let ManagedAccountType::CoinJoin { + external_addresses, + .. + } = managed_account.managed_account_type_mut() + { + external_addresses + .next_unused(&KeySource::Public(coinjoin_xpub), true) + .expect("coinjoin address") + } else { + panic!("Expected CoinJoin account type"); + } + }; + + let unlock_value = 100_000_000u64; + let asset_unlock_tx = Transaction { + version: 3, + lock_time: 0, + input: vec![TxIn { + previous_output: OutPoint { + txid: Txid::from_byte_array([1u8; 32]), + vout: 0, + }, + script_sig: ScriptBuf::new(), + sequence: 0xffffffff, + witness: dashcore::Witness::new(), + }], + output: vec![TxOut { + value: unlock_value, + script_pubkey: coinjoin_address.script_pubkey(), + }], + special_transaction_payload: Some(TransactionPayload::AssetUnlockPayloadType( + AssetUnlockPayload { + base: AssetUnlockBasePayload { + version: 1, + index: 42, + fee: 1000, + }, + request_info: AssetUnlockRequestInfo { + request_height: 500_000, + quorum_hash: [5u8; 32].into(), + }, + quorum_sig: BLSSignature::from([6u8; 96]), + }, + )), + }; + assert_eq!( + TransactionRouter::classify_transaction(&asset_unlock_tx), + TransactionType::AssetUnlock, + "tx must classify as AssetUnlock so it routes through the AssetUnlock arm" + ); + + // Use InBlock (not chainlocked) so the full record is retained under the + // default `keep-finalized-transactions=OFF` feature; the load-bearing + // assertions are UTXO creation and confirmed balance credit. + let context = TransactionContext::InBlock(BlockInfo::new( + 500_100, + BlockHash::from_slice(&[10u8; 32]).expect("Should create block hash"), + 1_650_000_300, + )); + let result = managed_wallet + .check_core_transaction(&asset_unlock_tx, context, &mut wallet, true, true) + .await; + managed_wallet.update_last_processed_height(500_100); + + assert!( + result.is_relevant, + "asset unlock paying a CoinJoin address must be relevant" + ); + assert_eq!( + result.total_received, unlock_value, + "asset unlock must credit the CoinJoin output value" + ); + + let coinjoin_account = + managed_wallet.first_coinjoin_managed_account().expect("coinjoin account"); + assert!( + coinjoin_account.transactions().contains_key(&asset_unlock_tx.txid()), + "asset unlock must be recorded on the CoinJoin account" + ); + assert_eq!( + coinjoin_account.utxos.len(), + 1, + "asset unlock must create a CoinJoin UTXO" + ); + + assert_eq!( + managed_wallet.balance.confirmed(), + unlock_value, + "confirmed balance must credit the CoinJoin asset-unlock withdrawal" + ); + assert_eq!( + managed_wallet.balance.total(), + unlock_value, + "total balance must include the CoinJoin asset-unlock credit" + ); + } + /// Test the full coinbase maturity flow - immature to mature transition #[tokio::test] async fn test_wallet_checker_immature_transaction_flow() { From 8d09ce121d1c8ec4501641edf190958c64fc97e8 Mon Sep 17 00:00:00 2001 From: PastaClaw Date: Thu, 16 Jul 2026 08:32:28 -0500 Subject: [PATCH 3/4] style(key-wallet): rustfmt wallet_checker CoinJoin credit tests --- .../transaction_checking/wallet_checker.rs | 22 ++++--------------- 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/key-wallet/src/transaction_checking/wallet_checker.rs b/key-wallet/src/transaction_checking/wallet_checker.rs index bc48c52f1..f42cdd663 100644 --- a/key-wallet/src/transaction_checking/wallet_checker.rs +++ b/key-wallet/src/transaction_checking/wallet_checker.rs @@ -831,10 +831,7 @@ mod tests { managed_wallet.update_last_processed_height(block_height); assert!(result.is_relevant, "coinbase paying a CoinJoin address must be relevant"); - assert_eq!( - result.total_received, reward, - "coinbase must credit the CoinJoin output value" - ); + assert_eq!(result.total_received, reward, "coinbase must credit the CoinJoin output value"); let coinjoin_account = managed_wallet.first_coinjoin_managed_account().expect("coinjoin account"); @@ -842,11 +839,7 @@ mod tests { coinjoin_account.transactions().contains_key(&coinbase_tx.txid()), "coinbase must be recorded on the CoinJoin account" ); - assert_eq!( - coinjoin_account.utxos.len(), - 1, - "coinbase must create a CoinJoin UTXO" - ); + assert_eq!(coinjoin_account.utxos.len(), 1, "coinbase must create a CoinJoin UTXO"); let utxo = coinjoin_account.utxos.values().next().expect("CoinJoin UTXO"); assert!(utxo.is_coinbase, "credited UTXO must be marked coinbase"); @@ -965,10 +958,7 @@ mod tests { .await; managed_wallet.update_last_processed_height(500_100); - assert!( - result.is_relevant, - "asset unlock paying a CoinJoin address must be relevant" - ); + assert!(result.is_relevant, "asset unlock paying a CoinJoin address must be relevant"); assert_eq!( result.total_received, unlock_value, "asset unlock must credit the CoinJoin output value" @@ -980,11 +970,7 @@ mod tests { coinjoin_account.transactions().contains_key(&asset_unlock_tx.txid()), "asset unlock must be recorded on the CoinJoin account" ); - assert_eq!( - coinjoin_account.utxos.len(), - 1, - "asset unlock must create a CoinJoin UTXO" - ); + assert_eq!(coinjoin_account.utxos.len(), 1, "asset unlock must create a CoinJoin UTXO"); assert_eq!( managed_wallet.balance.confirmed(), From b0c9aecdb3bc0b90c4fb47de8d51b1fafa1b669a Mon Sep 17 00:00:00 2001 From: PastaClaw Date: Thu, 16 Jul 2026 08:34:52 -0500 Subject: [PATCH 4/4] refactor(key-wallet): share CoinJoin credit-test setup and routing asserts Extract wallet_with_coinjoin_address() for the #900 credit regressions and assert Coinbase/AssetUnlock routing against fund_bearing_account_types() instead of re-listing the five types. Keeps the production list as the single source of truth for those unit tests. --- .../transaction_router/mod.rs | 4 +- .../transaction_router/tests/asset_unlock.rs | 22 ++--- .../transaction_router/tests/coinbase.rs | 11 +-- .../transaction_checking/wallet_checker.rs | 96 ++++++------------- 4 files changed, 46 insertions(+), 87 deletions(-) diff --git a/key-wallet/src/transaction_checking/transaction_router/mod.rs b/key-wallet/src/transaction_checking/transaction_router/mod.rs index d974ac3a3..0b2ebc0d9 100644 --- a/key-wallet/src/transaction_checking/transaction_router/mod.rs +++ b/key-wallet/src/transaction_checking/transaction_router/mod.rs @@ -88,7 +88,9 @@ impl TransactionRouter { /// label, never a precondition for discovery, so both shapes must consult the full set of /// fund-bearing accounts. An account only matches when a scriptPubKey or spent UTXO actually /// belongs to it, so checking extra accounts never produces false positives. - fn fund_bearing_account_types() -> Vec { + /// Visible to unit tests so routing assertions can compare against the + /// production list rather than re-listing the five types by hand. + pub(crate) fn fund_bearing_account_types() -> Vec { vec![ AccountTypeToCheck::StandardBIP44, AccountTypeToCheck::StandardBIP32, diff --git a/key-wallet/src/transaction_checking/transaction_router/tests/asset_unlock.rs b/key-wallet/src/transaction_checking/transaction_router/tests/asset_unlock.rs index 9b911342f..1ff4205c3 100644 --- a/key-wallet/src/transaction_checking/transaction_router/tests/asset_unlock.rs +++ b/key-wallet/src/transaction_checking/transaction_router/tests/asset_unlock.rs @@ -23,12 +23,11 @@ fn test_asset_unlock_routing() { // Asset unlock withdrawals can pay any fund-bearing address (destination is // user-chosen), so routing must cover the full fund-bearing set. - assert_eq!(accounts.len(), 5, "AssetUnlock should route to all fund-bearing account types"); - assert!(accounts.contains(&AccountTypeToCheck::StandardBIP44)); - assert!(accounts.contains(&AccountTypeToCheck::StandardBIP32)); - assert!(accounts.contains(&AccountTypeToCheck::CoinJoin)); - assert!(accounts.contains(&AccountTypeToCheck::DashpayReceivingFunds)); - assert!(accounts.contains(&AccountTypeToCheck::DashpayExternalAccount)); + assert_eq!( + accounts, + TransactionRouter::fund_bearing_account_types(), + "AssetUnlock should route to all fund-bearing account types" + ); // Should NOT check identity accounts - those are for locks only assert!(!accounts.contains(&AccountTypeToCheck::IdentityRegistration)); @@ -66,12 +65,11 @@ fn test_asset_unlock_classification() { // Verify routing for AssetUnlock let accounts = TransactionRouter::get_relevant_account_types(&tx_type); - assert_eq!(accounts.len(), 5, "AssetUnlock should route to all fund-bearing account types"); - assert!(accounts.contains(&AccountTypeToCheck::StandardBIP44)); - assert!(accounts.contains(&AccountTypeToCheck::StandardBIP32)); - assert!(accounts.contains(&AccountTypeToCheck::CoinJoin)); - assert!(accounts.contains(&AccountTypeToCheck::DashpayReceivingFunds)); - assert!(accounts.contains(&AccountTypeToCheck::DashpayExternalAccount)); + assert_eq!( + accounts, + TransactionRouter::fund_bearing_account_types(), + "AssetUnlock should route to all fund-bearing account types" + ); } #[tokio::test] diff --git a/key-wallet/src/transaction_checking/transaction_router/tests/coinbase.rs b/key-wallet/src/transaction_checking/transaction_router/tests/coinbase.rs index d543a061d..0d59d675c 100644 --- a/key-wallet/src/transaction_checking/transaction_router/tests/coinbase.rs +++ b/key-wallet/src/transaction_checking/transaction_router/tests/coinbase.rs @@ -275,12 +275,11 @@ fn test_coinbase_routing() { // Coinbase can pay any fund-bearing address (mining reward / masternode // payout is user-chosen), so routing must cover the full fund-bearing set. - assert_eq!(accounts.len(), 5, "Coinbase should route to all fund-bearing account types"); - assert!(accounts.contains(&AccountTypeToCheck::StandardBIP44)); - assert!(accounts.contains(&AccountTypeToCheck::StandardBIP32)); - assert!(accounts.contains(&AccountTypeToCheck::CoinJoin)); - assert!(accounts.contains(&AccountTypeToCheck::DashpayReceivingFunds)); - assert!(accounts.contains(&AccountTypeToCheck::DashpayExternalAccount)); + assert_eq!( + accounts, + TransactionRouter::fund_bearing_account_types(), + "Coinbase should route to all fund-bearing account types" + ); // Should NOT route to non-fund-bearing special account types assert!(!accounts.contains(&AccountTypeToCheck::IdentityRegistration)); diff --git a/key-wallet/src/transaction_checking/wallet_checker.rs b/key-wallet/src/transaction_checking/wallet_checker.rs index f42cdd663..15717d5b0 100644 --- a/key-wallet/src/transaction_checking/wallet_checker.rs +++ b/key-wallet/src/transaction_checking/wallet_checker.rs @@ -257,6 +257,32 @@ mod tests { use dashcore::{Address, BlockHash, TxIn, Txid}; use dashcore_hashes::Hash; + /// Wallet with a single CoinJoin account and one derived external address. + /// Shared fixture for credit/debit regressions that target CoinJoin ownership. + fn wallet_with_coinjoin_address() -> (Wallet, ManagedWalletInfo, Address) { + let network = Network::Testnet; + let mut wallet = Wallet::new_random(network, WalletAccountCreationOptions::None) + .expect("Should create wallet"); + wallet + .add_account( + AccountType::CoinJoin { + index: 0, + }, + None, + ) + .expect("Should add CoinJoin account"); + let mut managed_wallet = + ManagedWalletInfo::from_wallet_with_name(&wallet, "Test".to_string(), 0); + let coinjoin_xpub = + wallet.accounts.coinjoin_accounts.get(&0).expect("coinjoin account").account_xpub; + let coinjoin_address = managed_wallet + .first_coinjoin_managed_account_mut() + .expect("managed coinjoin") + .next_address(Some(&coinjoin_xpub), true) + .expect("coinjoin address"); + (wallet, managed_wallet, coinjoin_address) + } + /// Test wallet checker with unrelated transaction #[tokio::test] async fn test_wallet_checker_unrelated_transaction() { @@ -757,42 +783,9 @@ mod tests { /// yield a false positive. #[tokio::test] async fn test_coinbase_paying_coinjoin_address_is_credited() { - use crate::managed_account::managed_account_type::ManagedAccountType; use crate::transaction_checking::transaction_router::TransactionRouter; - let network = Network::Testnet; - - let mut wallet = Wallet::new_random(network, WalletAccountCreationOptions::None) - .expect("Should create wallet"); - wallet - .add_account( - AccountType::CoinJoin { - index: 0, - }, - None, - ) - .expect("Should add CoinJoin account"); - - let mut managed_wallet = - ManagedWalletInfo::from_wallet_with_name(&wallet, "Test".to_string(), 0); - - let coinjoin_xpub = - wallet.accounts.coinjoin_accounts.get(&0).expect("coinjoin account").account_xpub; - let coinjoin_address = { - let managed_account = - managed_wallet.first_coinjoin_managed_account_mut().expect("managed coinjoin"); - if let ManagedAccountType::CoinJoin { - external_addresses, - .. - } = managed_account.managed_account_type_mut() - { - external_addresses - .next_unused(&KeySource::Public(coinjoin_xpub), true) - .expect("coinjoin address") - } else { - panic!("Expected CoinJoin account type"); - } - }; + let (mut wallet, mut managed_wallet, coinjoin_address) = wallet_with_coinjoin_address(); let reward = 5_000_000_000u64; let coinbase_tx = Transaction { @@ -865,7 +858,6 @@ mod tests { /// a CoinJoin address was never credited (dashpay/rust-dashcore#900). #[tokio::test] async fn test_asset_unlock_paying_coinjoin_address_is_credited() { - use crate::managed_account::managed_account_type::ManagedAccountType; use crate::transaction_checking::transaction_router::TransactionRouter; use dashcore::blockdata::transaction::special_transaction::asset_unlock::qualified_asset_unlock::AssetUnlockPayload; use dashcore::blockdata::transaction::special_transaction::asset_unlock::request_info::AssetUnlockRequestInfo; @@ -873,39 +865,7 @@ mod tests { use dashcore::blockdata::transaction::special_transaction::TransactionPayload; use dashcore::bls_sig_utils::BLSSignature; - let network = Network::Testnet; - - let mut wallet = Wallet::new_random(network, WalletAccountCreationOptions::None) - .expect("Should create wallet"); - wallet - .add_account( - AccountType::CoinJoin { - index: 0, - }, - None, - ) - .expect("Should add CoinJoin account"); - - let mut managed_wallet = - ManagedWalletInfo::from_wallet_with_name(&wallet, "Test".to_string(), 0); - - let coinjoin_xpub = - wallet.accounts.coinjoin_accounts.get(&0).expect("coinjoin account").account_xpub; - let coinjoin_address = { - let managed_account = - managed_wallet.first_coinjoin_managed_account_mut().expect("managed coinjoin"); - if let ManagedAccountType::CoinJoin { - external_addresses, - .. - } = managed_account.managed_account_type_mut() - { - external_addresses - .next_unused(&KeySource::Public(coinjoin_xpub), true) - .expect("coinjoin address") - } else { - panic!("Expected CoinJoin account type"); - } - }; + let (mut wallet, mut managed_wallet, coinjoin_address) = wallet_with_coinjoin_address(); let unlock_value = 100_000_000u64; let asset_unlock_tx = Transaction {