1515
1616use std:: {
1717 cmp:: Reverse ,
18- collections:: { btree_map:: Entry , BTreeMap , BTreeSet } ,
18+ collections:: { btree_map:: Entry , BTreeMap , BTreeSet , VecDeque } ,
1919 ops:: Add ,
2020} ;
2121
@@ -749,7 +749,7 @@ impl OutputCache {
749749 & mut self ,
750750 confirmed_tx : & Transaction ,
751751 block_id : Id < GenBlock > ,
752- ) -> WalletResult < Vec < Id < Transaction > > > {
752+ ) -> WalletResult < Vec < ( Id < Transaction > , WalletTx ) > > {
753753 struct ConflictCheck {
754754 frozen_token_id : Option < TokenId > ,
755755 confirmed_account_nonce : Option < ( AccountType , AccountNonce ) > ,
@@ -800,58 +800,67 @@ impl OutputCache {
800800 for unconfirmed in self . unconfirmed_descendants . keys ( ) {
801801 let unconfirmed_tx = self . txs . get ( unconfirmed) . expect ( "must be present" ) ;
802802
803- if let WalletTx :: Tx ( tx) = unconfirmed_tx {
804- if let Some ( frozen_token_id) = conflict_check. frozen_token_id {
805- if self . violates_frozen_token ( unconfirmed_tx, & frozen_token_id) {
806- conflicting_txs. insert ( tx. get_transaction ( ) . get_id ( ) ) ;
807- continue ;
803+ match unconfirmed_tx {
804+ WalletTx :: Tx ( tx) => {
805+ if let Some ( frozen_token_id) = conflict_check. frozen_token_id {
806+ if self . violates_frozen_token ( unconfirmed_tx, & frozen_token_id) {
807+ conflicting_txs. insert ( tx. get_transaction ( ) . get_id ( ) ) ;
808+ continue ;
809+ }
808810 }
809- }
810811
811- if let Some ( ( confirmed_account, confirmed_account_nonce) ) =
812- conflict_check. confirmed_account_nonce
813- {
814- if confirmed_tx. get_id ( ) != tx. get_transaction ( ) . get_id ( )
815- && uses_conflicting_nonce (
816- unconfirmed_tx,
817- confirmed_account,
818- confirmed_account_nonce,
819- )
812+ if let Some ( ( confirmed_account, confirmed_account_nonce) ) =
813+ conflict_check. confirmed_account_nonce
820814 {
821- conflicting_txs. insert ( tx. get_transaction ( ) . get_id ( ) ) ;
822- continue ;
815+ if confirmed_tx. get_id ( ) != tx. get_transaction ( ) . get_id ( )
816+ && uses_conflicting_nonce (
817+ unconfirmed_tx,
818+ confirmed_account,
819+ confirmed_account_nonce,
820+ )
821+ {
822+ conflicting_txs. insert ( tx. get_transaction ( ) . get_id ( ) ) ;
823+ continue ;
824+ }
823825 }
824826 }
825- }
827+ WalletTx :: Block ( _) => {
828+ utils:: debug_panic_or_log!( "Cannot be block reward" ) ;
829+ }
830+ } ;
826831 }
827832 }
828833
829834 // Remove all descendants of conflicting txs
835+ let mut conflicting_tx_ids_with_descendants = vec ! [ ] ;
830836 let mut conflicting_txs_with_descendants = vec ! [ ] ;
831837
832838 for conflicting_tx in conflicting_txs {
833- let txs_to_rollback = self . remove_from_unconfirmed_descendants ( conflicting_tx) ;
839+ let tx_ids_to_rollback = self . remove_from_unconfirmed_descendants ( conflicting_tx) ;
834840
835841 // Mark conflicting tx and its descendants as Conflicting and update OutputCache data accordingly
836842 let mut tx_to_rollback_data = vec ! [ ] ;
837- for tx_id in txs_to_rollback . iter ( ) . rev ( ) . copied ( ) {
843+ for tx_id in tx_ids_to_rollback . iter ( ) . rev ( ) . copied ( ) {
838844 match self . txs . entry ( tx_id. into ( ) ) {
839845 Entry :: Occupied ( mut entry) => match entry. get_mut ( ) {
840846 WalletTx :: Block ( _) => {
841847 Err ( WalletError :: TransactionIdCannotMapToBlock ( tx_id) )
842848 }
843- WalletTx :: Tx ( tx) => match tx. state ( ) {
844- TxState :: Inactive ( _) | TxState :: InMempool ( _) => {
845- tx. set_state ( TxState :: Conflicted ( block_id) ) ;
846- tx_to_rollback_data. push ( entry. get ( ) . clone ( ) ) ;
847- Ok ( ( ) )
849+ WalletTx :: Tx ( tx) => {
850+ conflicting_txs_with_descendants. push ( WalletTx :: Tx ( tx. clone ( ) ) ) ;
851+ match tx. state ( ) {
852+ TxState :: Inactive ( _) | TxState :: InMempool ( _) => {
853+ tx. set_state ( TxState :: Conflicted ( block_id) ) ;
854+ tx_to_rollback_data. push ( entry. get ( ) . clone ( ) ) ;
855+ Ok ( ( ) )
856+ }
857+ TxState :: Conflicted ( ..)
858+ | TxState :: Abandoned
859+ | TxState :: Confirmed ( ..) => {
860+ Err ( WalletError :: CannotMarkTxAsConflictedIfInState ( * tx. state ( ) ) )
861+ }
848862 }
849- TxState :: Conflicted ( ..)
850- | TxState :: Abandoned
851- | TxState :: Confirmed ( ..) => {
852- Err ( WalletError :: CannotMarkTxAsConflictedIfInState ( * tx. state ( ) ) )
853- }
854- } ,
863+ }
855864 } ,
856865 Entry :: Vacant ( _) => {
857866 Err ( WalletError :: CannotFindDescendantTransactionWithId ( tx_id) )
@@ -863,10 +872,13 @@ impl OutputCache {
863872 self . rollback_tx_data ( & tx) ?;
864873 }
865874
866- conflicting_txs_with_descendants . extend ( txs_to_rollback . into_iter ( ) ) ;
875+ conflicting_tx_ids_with_descendants . extend ( tx_ids_to_rollback . into_iter ( ) ) ;
867876 }
868877
869- Ok ( conflicting_txs_with_descendants)
878+ Ok ( conflicting_tx_ids_with_descendants
879+ . into_iter ( )
880+ . zip_eq ( conflicting_txs_with_descendants. into_iter ( ) )
881+ . collect ( ) )
870882 }
871883
872884 fn violates_frozen_token ( & self , unconfirmed_tx : & WalletTx , frozen_token_id : & TokenId ) -> bool {
@@ -933,9 +945,9 @@ impl OutputCache {
933945 TxState :: Confirmed ( _, _, _) => false ,
934946 } ;
935947
936- if is_unconfirmed && !already_present {
937- self . unconfirmed_descendants . insert ( tx_id. clone ( ) , BTreeSet :: new ( ) ) ;
938- } else if !is_unconfirmed {
948+ if is_unconfirmed {
949+ self . unconfirmed_descendants . entry ( tx_id. clone ( ) ) . or_default ( ) ;
950+ } else {
939951 self . unconfirmed_descendants . remove ( & tx_id) ;
940952 }
941953
@@ -1438,16 +1450,21 @@ impl OutputCache {
14381450 }
14391451
14401452 // Removes a tx and all its descendants from unconfirmed descendants collection.
1441- // Returns provided tx and all the descendants in that specific order.
1453+ // Returns provided tx and all the descendants in such way that parent always comes before its
1454+ // descendants. Returned collection contains no duplicates.
14421455 fn remove_from_unconfirmed_descendants (
14431456 & mut self ,
14441457 tx_id : Id < Transaction > ,
14451458 ) -> Vec < Id < Transaction > > {
14461459 let mut all_txs = Vec :: new ( ) ;
1447- let mut to_update = BTreeSet :: from_iter ( [ OutPointSourceId :: from ( tx_id) ] ) ;
1460+ let mut all_txs_as_set = BTreeSet :: new ( ) ;
1461+ let mut to_update: VecDeque < OutPointSourceId > = [ tx_id. into ( ) ] . into ( ) ;
14481462
1449- while let Some ( outpoint_source_id) = to_update. pop_first ( ) {
1450- all_txs. push ( * outpoint_source_id. get_tx_id ( ) . expect ( "must be a transaction" ) ) ;
1463+ while let Some ( outpoint_source_id) = to_update. pop_front ( ) {
1464+ let tx_id = * outpoint_source_id. get_tx_id ( ) . expect ( "must be a transaction" ) ;
1465+ if all_txs_as_set. insert ( tx_id) {
1466+ all_txs. push ( tx_id) ;
1467+ }
14511468
14521469 if let Some ( descendants) = self . unconfirmed_descendants . remove ( & outpoint_source_id) {
14531470 to_update. extend ( descendants. into_iter ( ) )
@@ -1501,7 +1518,7 @@ impl OutputCache {
15011518 }
15021519 }
15031520
1504- for output in tx. outputs ( ) {
1521+ for output in tx. outputs ( ) . iter ( ) . rev ( ) {
15051522 match output {
15061523 TxOutput :: CreateStakePool ( pool_id, _) => {
15071524 self . pools . remove ( pool_id) ;
@@ -1542,43 +1559,47 @@ impl OutputCache {
15421559 }
15431560
15441561 /// Mark a transaction and its descendants as abandoned
1545- /// Returns a Vec of the transaction Ids that have been abandoned
1562+ /// Returns a Vec of the transactions and the Ids that have been abandoned
15461563 pub fn abandon_transaction (
15471564 & mut self ,
15481565 tx_id : Id < Transaction > ,
1549- ) -> WalletResult < Vec < Id < Transaction > > > {
1566+ ) -> WalletResult < Vec < ( Id < Transaction > , WalletTx ) > > {
15501567 let all_abandoned = self . remove_from_unconfirmed_descendants ( tx_id) ;
1568+ let mut all_abandoned_txs = Vec :: with_capacity ( all_abandoned. len ( ) ) ;
15511569
15521570 let mut txs_to_rollback = vec ! [ ] ;
15531571 for tx_id in all_abandoned. iter ( ) . rev ( ) . copied ( ) {
15541572 match self . txs . entry ( tx_id. into ( ) ) {
15551573 Entry :: Occupied ( mut entry) => match entry. get_mut ( ) {
15561574 WalletTx :: Block ( _) => Err ( WalletError :: CannotFindTransactionWithId ( tx_id) ) ,
1557- WalletTx :: Tx ( tx) => match tx. state ( ) {
1558- TxState :: Inactive ( _) => {
1559- tx. set_state ( TxState :: Abandoned ) ;
1560- txs_to_rollback. push ( entry. get ( ) . clone ( ) ) ;
1561- Ok ( ( ) )
1562- }
1563- TxState :: Conflicted ( _) => {
1564- tx. set_state ( TxState :: Abandoned ) ;
1565- Ok ( ( ) )
1575+ WalletTx :: Tx ( tx) => {
1576+ all_abandoned_txs. push ( WalletTx :: Tx ( tx. clone ( ) ) ) ;
1577+ match tx. state ( ) {
1578+ TxState :: Inactive ( _) => {
1579+ tx. set_state ( TxState :: Abandoned ) ;
1580+ txs_to_rollback. push ( entry. get ( ) . clone ( ) ) ;
1581+ Ok ( ( ) )
1582+ }
1583+ TxState :: Conflicted ( _) => {
1584+ tx. set_state ( TxState :: Abandoned ) ;
1585+ Ok ( ( ) )
1586+ }
1587+ state => Err ( WalletError :: CannotChangeTransactionState (
1588+ * state,
1589+ TxState :: Abandoned ,
1590+ ) ) ,
15661591 }
1567- state => Err ( WalletError :: CannotChangeTransactionState (
1568- * state,
1569- TxState :: Abandoned ,
1570- ) ) ,
1571- } ,
1592+ }
15721593 } ,
15731594 Entry :: Vacant ( _) => Err ( WalletError :: CannotFindTransactionWithId ( tx_id) ) ,
15741595 } ?;
15751596 }
15761597
1577- for tx in txs_to_rollback {
1578- self . rollback_tx_data ( & tx) ?;
1598+ for tx in & txs_to_rollback {
1599+ self . rollback_tx_data ( tx) ?;
15791600 }
15801601
1581- Ok ( all_abandoned)
1602+ Ok ( all_abandoned. into_iter ( ) . zip_eq ( all_abandoned_txs . into_iter ( ) ) . collect ( ) )
15821603 }
15831604
15841605 pub fn get_transaction ( & self , transaction_id : Id < Transaction > ) -> WalletResult < & TxData > {
@@ -1840,3 +1861,6 @@ fn uses_conflicting_nonce(
18401861 }
18411862 } )
18421863}
1864+
1865+ #[ cfg( test) ]
1866+ mod tests;
0 commit comments