@@ -126,7 +126,7 @@ impl<T: TransactionOrdering> BestTransactions<T> {
126126 }
127127
128128 /// Non-blocking read on the new pending transactions subscription channel
129- fn try_recv ( & mut self ) -> Option < PendingTransaction < T > > {
129+ fn try_recv ( & mut self ) -> Option < IncomingTransaction < T > > {
130130 loop {
131131 match self . new_transaction_receiver . as_mut ( ) ?. try_recv ( ) {
132132 Ok ( tx) => {
@@ -135,9 +135,9 @@ impl<T: TransactionOrdering> BestTransactions<T> {
135135 {
136136 // we skip transactions if we already yielded a transaction with lower
137137 // priority
138- return None
138+ return Some ( IncomingTransaction :: Stash ( tx ) )
139139 }
140- return Some ( tx )
140+ return Some ( IncomingTransaction :: Process ( tx ) )
141141 }
142142 // note TryRecvError::Lagged can be returned here, which is an error that attempts
143143 // to correct itself on consecutive try_recv() attempts
@@ -170,18 +170,54 @@ impl<T: TransactionOrdering> BestTransactions<T> {
170170 for _ in 0 ..MAX_NEW_TRANSACTIONS_PER_BATCH {
171171 if let Some ( pending_tx) = self . try_recv ( ) {
172172 // same logic as PendingPool::add_transaction/PendingPool::best_with_unlocked
173- let tx_id = * pending_tx. transaction . id ( ) ;
174- if self . ancestor ( & tx_id) . is_none ( ) {
175- self . independent . insert ( pending_tx. clone ( ) ) ;
173+
174+ match pending_tx {
175+ IncomingTransaction :: Process ( tx) => {
176+ let tx_id = * tx. transaction . id ( ) ;
177+ if self . ancestor ( & tx_id) . is_none ( ) {
178+ self . independent . insert ( tx. clone ( ) ) ;
179+ }
180+ self . all . insert ( tx_id, tx) ;
181+ }
182+ IncomingTransaction :: Stash ( tx) => {
183+ let tx_id = * tx. transaction . id ( ) ;
184+ self . all . insert ( tx_id, tx) ;
185+ }
176186 }
177- self . all . insert ( tx_id, pending_tx) ;
178187 } else {
179188 break ;
180189 }
181190 }
182191 }
183192}
184193
194+ /// Result of attempting to receive a new transaction from the channel during iteration.
195+ ///
196+ /// This enum determines how a newly received transaction should be handled based on its priority
197+ /// relative to transactions already yielded by the iterator.
198+ enum IncomingTransaction < T : TransactionOrdering > {
199+ /// Process the transaction normally: add to both `all` map and potentially to `independent`
200+ /// set (if it has no ancestor).
201+ ///
202+ /// This variant is used when the transaction's priority is lower than or equal to the last
203+ /// yielded transaction, meaning it can be safely processed without breaking the descending
204+ /// priority order.
205+ Process ( PendingTransaction < T > ) ,
206+
207+ /// Stash the transaction: add only to the `all` map, but NOT to the `independent` set.
208+ ///
209+ /// This variant is used when the transaction has a higher priority than the last yielded
210+ /// transaction. We cannot yield it immediately (to maintain strict priority ordering), but we
211+ /// must still track it so that:
212+ /// - Its descendants can find it via `ancestor()` lookups
213+ /// - We prevent those descendants from being incorrectly promoted to `independent`
214+ ///
215+ /// Without stashing, if a child of this transaction arrives later, it would fail to find its
216+ /// parent in `all`, be marked as `independent`, and be yielded out of order (before its
217+ /// parent), causing nonce gaps.
218+ Stash ( PendingTransaction < T > ) ,
219+ }
220+
185221impl < T : TransactionOrdering > crate :: traits:: BestTransactions for BestTransactions < T > {
186222 fn mark_invalid ( & mut self , tx : & Self :: Item , kind : InvalidPoolTransactionError ) {
187223 Self :: mark_invalid ( self , tx, kind)
0 commit comments