@@ -592,19 +592,59 @@ impl<T: WalletInfoInterface + Send + Sync + 'static> WalletManager<T> {
592592#[ cfg( test) ]
593593mod tests {
594594 use super :: * ;
595+ use crate :: matching:: { check_compact_filters_for_elements, FilterMatchKey } ;
595596 use crate :: test_helpers:: * ;
597+ use dashcore:: bip158:: BlockFilter ;
596598 use dashcore:: block:: { Header , Version } ;
597599 use dashcore:: hashes:: Hash ;
598600 use dashcore:: pow:: CompactTarget ;
599601 use dashcore:: {
600602 BlockHash , Network , OutPoint , ScriptBuf , TxIn , TxMerkleNode , TxOut , Txid , Witness ,
601603 } ;
604+ use key_wallet:: account:: ManagedAccountTrait as _;
602605 use key_wallet:: account:: StandardAccountType ;
603606 use key_wallet:: mnemonic:: Language ;
604607 use key_wallet:: wallet:: initialization:: WalletAccountCreationOptions ;
605608 use key_wallet:: wallet:: managed_wallet_info:: transaction_building:: AccountTypePreference ;
606609 use key_wallet:: wallet:: managed_wallet_info:: ManagedWalletInfo ;
607610 use key_wallet:: { AccountType , Mnemonic } ;
611+ use std:: collections:: HashMap ;
612+
613+ fn coinjoin_account < ' a > (
614+ manager : & ' a WalletManager < ManagedWalletInfo > ,
615+ wallet_id : & WalletId ,
616+ ) -> & ' a key_wallet:: managed_account:: ManagedCoreFundsAccount {
617+ manager
618+ . get_wallet_info ( wallet_id)
619+ . expect ( "wallet info" )
620+ . accounts
621+ . coinjoin_accounts
622+ . get ( & 0 )
623+ . expect ( "CoinJoin account 0" )
624+ }
625+
626+ fn spend_first_output_of ( tx : & Transaction ) -> Transaction {
627+ Transaction {
628+ version : 2 ,
629+ lock_time : 0 ,
630+ input : vec ! [ TxIn {
631+ previous_output: OutPoint {
632+ txid: tx. txid( ) ,
633+ vout: 0 ,
634+ } ,
635+ script_sig: ScriptBuf :: new( ) ,
636+ sequence: u32 :: MAX ,
637+ witness: Witness :: default ( ) ,
638+ } ] ,
639+ output : vec ! [ TxOut {
640+ value: tx. output[ 0 ] . value,
641+ script_pubkey: ScriptBuf :: new_p2pkh( & dashcore:: PubkeyHash :: from_byte_array(
642+ [ 0x77 ; 20 ] ,
643+ ) ) ,
644+ } ] ,
645+ special_transaction_payload : None ,
646+ }
647+ }
608648
609649 fn make_block ( txdata : Vec < Transaction > ) -> Block {
610650 Block {
@@ -820,32 +860,57 @@ mod tests {
820860 ) ;
821861 }
822862
863+ /// A CoinJoin address used and left with no unspent output was once
864+ /// dropped from the filter scan as unpayable. Mainnet pays such addresses
865+ /// again, and the scan then never matched the block carrying the payment.
823866 #[ tokio:: test]
824- async fn test_scan_script_pubkeys_for_prunes_spent_coinjoin_addresses ( ) {
825- use key_wallet:: account:: ManagedAccountTrait ;
826-
867+ async fn test_filter_scan_matches_a_second_payment_to_an_emptied_coinjoin_address ( ) {
827868 let ( mut manager, wallet_id, _addr) = setup_manager_with_wallet ( ) ;
869+ let wallets = BTreeSet :: from ( [ wallet_id] ) ;
870+
871+ let coinjoin_addr = coinjoin_account ( & manager, & wallet_id)
872+ . all_addresses ( )
873+ . first ( )
874+ . cloned ( )
875+ . expect ( "CoinJoin address" ) ;
876+
877+ let received = create_tx_paying_to ( & coinjoin_addr, 0x11 ) ;
878+ let block = Block :: dummy ( 100 , vec ! [ received. clone( ) ] ) ;
879+ manager. process_block_for_wallets ( & block, block. block_hash ( ) , 100 , & wallets) . await ;
880+
881+ let spend = spend_first_output_of ( & received) ;
882+ let block = Block :: dummy ( 101 , vec ! [ spend] ) ;
883+ manager. process_block_for_wallets ( & block, block. block_hash ( ) , 101 , & wallets) . await ;
828884
829- // Untouched wallet: the scan set equals the monitored set.
830- let monitored = manager. monitored_script_pubkeys_for ( & wallet_id) ;
831- assert_eq ! ( manager. scan_script_pubkeys_for( & wallet_id) , monitored) ;
832-
833- // Mark a CoinJoin address used with no unspent output — a spent
834- // single-use address. The scan query drops it; the monitored set
835- // keeps it.
836- let info = manager. get_wallet_info_mut ( & wallet_id) . expect ( "wallet info" ) ;
837- let coinjoin = info. accounts . coinjoin_accounts . get_mut ( & 0 ) . expect ( "CoinJoin account 0" ) ;
838- let spent_addr = coinjoin. all_addresses ( ) . first ( ) . cloned ( ) . expect ( "CoinJoin address" ) ;
839- assert ! ( coinjoin. mark_address_used( & spent_addr) ) ;
840-
841- let monitored = manager. monitored_script_pubkeys_for ( & wallet_id) ;
842- let scan = manager. scan_script_pubkeys_for ( & wallet_id) ;
843- assert ! ( monitored. contains( & spent_addr. script_pubkey( ) ) ) ;
844- assert ! ( !scan. contains( & spent_addr. script_pubkey( ) ) ) ;
845- assert_eq ! ( scan. len( ) , monitored. len( ) - 1 ) ;
846-
847- // Unknown wallet id yields an empty scan set.
885+ // Used, and holding nothing: the state that used to drop it.
886+ assert ! ( coinjoin_account( & manager, & wallet_id) . all_addresses( ) . contains( & coinjoin_addr) ) ;
887+ assert_eq ! ( manager. get_wallet_balance( & wallet_id) . expect( "balance" ) . total( ) , 0 ) ;
888+ assert_eq ! (
889+ manager. scan_script_pubkeys_for( & wallet_id) ,
890+ manager. monitored_script_pubkeys_for( & wallet_id)
891+ ) ;
848892 assert ! ( manager. scan_script_pubkeys_for( & [ 0xff ; 32 ] ) . is_empty( ) ) ;
893+
894+ // The block carrying the second payment must match the scan query.
895+ let later = Block :: dummy ( 102 , vec ! [ create_tx_paying_to( & coinjoin_addr, 0x33 ) ] ) ;
896+ let filters = HashMap :: from ( [ (
897+ FilterMatchKey :: new ( 102 , later. block_hash ( ) ) ,
898+ BlockFilter :: dummy ( & later) ,
899+ ) ] ) ;
900+ let matched = check_compact_filters_for_elements (
901+ & filters,
902+ & manager. scan_script_pubkeys_for ( & wallet_id) ,
903+ & [ ] ,
904+ 0 ,
905+ ) ;
906+ assert_eq ! ( matched. len( ) , 1 , "the scan query must still watch the emptied address" ) ;
907+
908+ manager. process_block_for_wallets ( & later, later. block_hash ( ) , 102 , & wallets) . await ;
909+ assert_eq ! (
910+ manager. get_wallet_balance( & wallet_id) . expect( "balance" ) . confirmed( ) ,
911+ TX_AMOUNT ,
912+ "the second payment must be credited"
913+ ) ;
849914 }
850915
851916 #[ tokio:: test]
0 commit comments