@@ -11,6 +11,7 @@ use crate::transaction_checking::{
1111 BlockInfo , TransactionContext , TransactionType , WalletTransactionChecker ,
1212} ;
1313use crate :: utxo:: Utxo ;
14+ use crate :: wallet:: managed_wallet_info:: wallet_info_interface:: WalletInfoInterface ;
1415use crate :: wallet:: managed_wallet_info:: { PersistedWalletState , RestoreError } ;
1516use crate :: wallet:: ManagedWalletInfo ;
1617use crate :: AccountType ;
@@ -789,3 +790,146 @@ fn should_reject_balance_overflow_before_installing_coins() {
789790 . is_err( ) ) ;
790791 assert ! ( wallet. first_bip44_managed_account( ) . unwrap( ) . utxos. is_empty( ) ) ;
791792}
793+
794+ #[ tokio:: test]
795+ async fn should_preserve_cross_account_input_recognition_after_restore ( ) {
796+ let mut template = TestWalletContext :: new_random ( ) ;
797+ let other_xpub = template. wallet . accounts . standard_bip32_accounts . get ( & 0 ) . unwrap ( ) . account_xpub ;
798+ let other_address = template
799+ . managed_wallet
800+ . first_bip32_managed_account_mut ( )
801+ . unwrap ( )
802+ . next_receive_address ( Some ( & other_xpub) , true )
803+ . unwrap ( ) ;
804+ let mut restored = template. managed_wallet . clone ( ) ;
805+ let funding = Transaction :: dummy ( & template. receive_address , 0 ..1 , & [ 1_000_000 ] ) ;
806+ let parent = OutPoint {
807+ txid : funding. txid ( ) ,
808+ vout : 0 ,
809+ } ;
810+ let mut spending = spend ( parent) ;
811+ spending. output [ 0 ] . script_pubkey = other_address. script_pubkey ( ) ;
812+ template
813+ . managed_wallet
814+ . check_core_transaction (
815+ & spending,
816+ TransactionContext :: Mempool ,
817+ & mut template. wallet ,
818+ true ,
819+ true ,
820+ )
821+ . await ;
822+ let context = TransactionContext :: InBlock ( BlockInfo :: new (
823+ 40 ,
824+ BlockHash :: from_byte_array ( [ 40 ; 32 ] ) ,
825+ 1_700_000_000 ,
826+ ) ) ;
827+ template
828+ . managed_wallet
829+ . check_core_transaction ( & funding, context. clone ( ) , & mut template. wallet , true , true )
830+ . await ;
831+ let records = template
832+ . managed_wallet
833+ . accounts
834+ . all_accounts ( )
835+ . into_iter ( )
836+ . flat_map ( |account| account. transactions ( ) . values ( ) . cloned ( ) )
837+ . collect ( ) ;
838+ restored
839+ . restore_persisted_state ( PersistedWalletState {
840+ transactions : records,
841+ utxos : template
842+ . managed_wallet
843+ . accounts
844+ . all_accounts ( )
845+ . into_iter ( )
846+ . filter_map ( |account| account. as_funds ( ) )
847+ . flat_map ( |account| {
848+ account
849+ . utxos
850+ . values ( )
851+ . cloned ( )
852+ . map ( |utxo| ( account. managed_account_type ( ) . to_account_type ( ) , utxo) )
853+ } )
854+ . collect ( ) ,
855+ additional_spent_outpoints : template
856+ . managed_wallet
857+ . observed_spent_outpoints ( )
858+ . iter ( )
859+ . map ( |( outpoint, height) | ( * outpoint, Some ( * height) ) )
860+ . chain ( [ ( parent, None ) ] )
861+ . collect ( ) ,
862+ } )
863+ . unwrap ( ) ;
864+ let live_result = template
865+ . managed_wallet
866+ . check_core_transaction ( & spending, context. clone ( ) , & mut template. wallet , true , true )
867+ . await ;
868+ let restored_result =
869+ restored. check_core_transaction ( & spending, context, & mut template. wallet , true , true ) . await ;
870+ assert_eq ! ( live_result. new_records. len( ) , 1 ) ;
871+ assert_eq ! ( live_result. new_records[ 0 ] . account_type, bip44( ) ) ;
872+ assert_eq ! ( live_result. new_records[ 0 ] . net_amount, -1_000_000 ) ;
873+ assert_eq ! ( live_result. new_records[ 0 ] . input_details. len( ) , 1 ) ;
874+ assert_eq ! ( restored_result. new_records. len( ) , live_result. new_records. len( ) ) ;
875+ let debit = & restored_result. new_records [ 0 ] ;
876+ assert_eq ! ( debit. account_type, bip44( ) ) ;
877+ assert_eq ! ( debit. net_amount, -1_000_000 ) ;
878+ assert_eq ! ( debit. input_details. len( ) , 1 ) ;
879+ assert_eq ! ( debit. input_details[ 0 ] . value, 1_000_000 ) ;
880+ }
881+
882+ #[ test]
883+ fn should_reject_coin_owned_by_another_wallet ( ) {
884+ let owner = TestWalletContext :: new_random ( ) ;
885+ let mut receiver = TestWalletContext :: new_random ( ) ;
886+ assert ! ( !receiver. bip44_account( ) . contains_address( & owner. receive_address) ) ;
887+ let tx = Transaction :: dummy ( & owner. receive_address , 0 ..1 , & [ 1_000_000 ] ) ;
888+ let outpoint = OutPoint {
889+ txid : tx. txid ( ) ,
890+ vout : 0 ,
891+ } ;
892+ let mut coin = Utxo :: new ( outpoint, tx. output [ 0 ] . clone ( ) , owner. receive_address , 100 , false ) ;
893+ coin. is_confirmed = true ;
894+ let result = receiver. managed_wallet . restore_persisted_state ( PersistedWalletState {
895+ utxos : vec ! [ ( bip44( ) , coin) ] ,
896+ ..Default :: default ( )
897+ } ) ;
898+ assert_eq ! ( result, Err ( RestoreError :: InvalidUtxo ( outpoint) ) ) ;
899+ assert ! ( receiver. bip44_account( ) . utxos. is_empty( ) ) ;
900+ }
901+
902+ #[ test]
903+ fn should_reject_coinbase_flag_disagreeing_with_record ( ) {
904+ let mut receiver = TestWalletContext :: new_random ( ) ;
905+ receiver. managed_wallet . update_last_processed_height ( 100 ) ;
906+ let mut tx = Transaction :: dummy ( & receiver. receive_address , 0 ..1 , & [ 1_000_000 ] ) ;
907+ tx. input [ 0 ] . previous_output = OutPoint :: null ( ) ;
908+ assert ! ( tx. is_coin_base( ) ) ;
909+ let outpoint = OutPoint {
910+ txid : tx. txid ( ) ,
911+ vout : 0 ,
912+ } ;
913+ let mut coin =
914+ Utxo :: new ( outpoint, tx. output [ 0 ] . clone ( ) , receiver. receive_address . clone ( ) , 100 , false ) ;
915+ coin. is_confirmed = true ;
916+ let record = TransactionRecord :: new (
917+ tx,
918+ bip44 ( ) ,
919+ TransactionContext :: InBlock ( BlockInfo :: new ( 100 , BlockHash :: all_zeros ( ) , 0 ) ) ,
920+ TransactionType :: Standard ,
921+ TransactionDirection :: Incoming ,
922+ vec ! [ ] ,
923+ vec ! [ ] ,
924+ 1_000_000 ,
925+ ) ;
926+ let result = receiver. managed_wallet . restore_persisted_state ( PersistedWalletState {
927+ transactions : vec ! [ record] ,
928+ utxos : vec ! [ ( bip44( ) , coin) ] ,
929+ ..Default :: default ( )
930+ } ) ;
931+ assert_eq ! ( result, Err ( RestoreError :: InvalidUtxo ( outpoint) ) ) ;
932+ assert ! ( receiver. bip44_account( ) . utxos. is_empty( ) ) ;
933+ assert ! ( receiver. bip44_account( ) . transactions( ) . is_empty( ) ) ;
934+ assert ! ( receiver. managed_wallet. observed_spent_outpoints( ) . is_empty( ) ) ;
935+ }
0 commit comments