1- use std:: {
2- collections:: { BTreeMap , VecDeque } ,
3- sync:: {
4- atomic:: { AtomicU64 , Ordering } ,
5- Arc ,
6- } ,
7- } ;
8-
9- use alloy_primitives:: Address ;
1+ use alloy_primitives:: { Address , TxHash } ;
102use reth_payload_util:: PayloadTransactions ;
113use reth_transaction_pool:: PoolTransaction ;
4+ use std:: collections:: HashSet ;
125use tracing:: debug;
136
147use crate :: tx:: MaybeFlashblockFilter ;
@@ -19,22 +12,36 @@ where
1912 I : PayloadTransactions < Transaction = T > ,
2013{
2114 inner : I ,
22- current_flashblock_number : Arc < AtomicU64 > ,
23- early_transactions : BTreeMap < u64 , VecDeque < T > > ,
15+ current_flashblock_number : u64 ,
16+ // Transactions that were already commited to the state. Using them again would cause NonceTooLow
17+ // so we skip them
18+ commited_transactions : HashSet < TxHash > ,
2419}
2520
2621impl < T , I > BestFlashblocksTxs < T , I >
2722where
2823 T : PoolTransaction ,
2924 I : PayloadTransactions < Transaction = T > ,
3025{
31- pub fn new ( inner : I , current_flashblock_number : Arc < AtomicU64 > ) -> Self {
26+ pub fn new ( inner : I ) -> Self {
3227 Self {
3328 inner,
34- current_flashblock_number,
35- early_transactions : Default :: default ( ) ,
29+ current_flashblock_number : 0 ,
30+ commited_transactions : Default :: default ( ) ,
3631 }
3732 }
33+
34+ /// Replaces current iterator with new one. We use it on new flashblock building, to refresh
35+ /// priority boundaries
36+ pub fn refresh_iterator ( & mut self , inner : I , current_flashblock_number : u64 ) {
37+ self . inner = inner;
38+ self . current_flashblock_number = current_flashblock_number;
39+ }
40+
41+ /// Remove transaction from next iteration and it already in the state
42+ pub fn mark_commited ( & mut self , txs : Vec < TxHash > ) {
43+ self . commited_transactions . extend ( txs) ;
44+ }
3845}
3946
4047impl < T , I > PayloadTransactions for BestFlashblocksTxs < T , I >
@@ -46,66 +53,30 @@ where
4653
4754 fn next ( & mut self , ctx : ( ) ) -> Option < Self :: Transaction > {
4855 loop {
49- let flashblock_number = self . current_flashblock_number . load ( Ordering :: Relaxed ) ;
50-
51- // Check for new transactions that can be executed with the higher flashblock number
52- while let Some ( ( & min_flashblock, _) ) = self . early_transactions . first_key_value ( ) {
53- if min_flashblock > flashblock_number {
54- break ;
55- }
56-
57- if let Some ( mut txs) = self . early_transactions . remove ( & min_flashblock) {
58- while let Some ( tx) = txs. pop_front ( ) {
59- // Re-check max flashblock number just in case
60- if let Some ( max) = tx. flashblock_number_max ( ) {
61- if flashblock_number > max {
62- debug ! (
63- target: "payload_builder" ,
64- sender = ?tx. sender( ) ,
65- nonce = tx. nonce( ) ,
66- current_flashblock = flashblock_number,
67- max_flashblock = max,
68- "Bundle flashblock max exceeded"
69- ) ;
70- self . mark_invalid ( tx. sender ( ) , tx. nonce ( ) ) ;
71- continue ;
72- }
73- }
74-
75- // The vecdeque isn't modified in place so we need to replace it
76- if !txs. is_empty ( ) {
77- self . early_transactions . insert ( min_flashblock, txs) ;
78- }
79-
80- return Some ( tx) ;
81- }
82- }
83- }
84-
8556 let tx = self . inner . next ( ctx) ?;
57+ // Skip transaction we already included
58+ if self . commited_transactions . contains ( tx. hash ( ) ) {
59+ continue ;
60+ }
8661
8762 let flashblock_number_min = tx. flashblock_number_min ( ) ;
8863 let flashblock_number_max = tx. flashblock_number_max ( ) ;
8964
9065 // Check min flashblock requirement
9166 if let Some ( min) = flashblock_number_min {
92- if flashblock_number < min {
93- self . early_transactions
94- . entry ( min)
95- . or_default ( )
96- . push_back ( tx) ;
67+ if self . current_flashblock_number < min {
9768 continue ;
9869 }
9970 }
10071
10172 // Check max flashblock requirement
10273 if let Some ( max) = flashblock_number_max {
103- if flashblock_number > max {
74+ if self . current_flashblock_number > max {
10475 debug ! (
10576 target: "payload_builder" ,
10677 sender = ?tx. sender( ) ,
10778 nonce = tx. nonce( ) ,
108- current_flashblock = flashblock_number ,
79+ current_flashblock = self . current_flashblock_number ,
10980 max_flashblock = max,
11081 "Bundle flashblock max exceeded"
11182 ) ;
@@ -118,15 +89,144 @@ where
11889 }
11990 }
12091
92+ /// Proxy to inner iterator
12193 fn mark_invalid ( & mut self , sender : Address , nonce : u64 ) {
12294 self . inner . mark_invalid ( sender, nonce) ;
95+ }
96+ }
97+
98+ #[ cfg( test) ]
99+ mod tests {
100+ use crate :: {
101+ builders:: flashblocks:: best_txs:: BestFlashblocksTxs ,
102+ mock_tx:: { MockFbTransaction , MockFbTransactionFactory } ,
103+ } ;
104+ use alloy_consensus:: Transaction ;
105+ use reth_payload_util:: { BestPayloadTransactions , PayloadTransactions } ;
106+ use reth_transaction_pool:: { pool:: PendingPool , CoinbaseTipOrdering , PoolTransaction } ;
107+ use std:: sync:: Arc ;
108+
109+ #[ test]
110+ fn test_simple_case ( ) {
111+ let mut pool = PendingPool :: new ( CoinbaseTipOrdering :: < MockFbTransaction > :: default ( ) ) ;
112+ let mut f = MockFbTransactionFactory :: default ( ) ;
113+
114+ // Add 3 regular transaction
115+ let tx_1 = f. create_eip1559 ( ) ;
116+ let tx_2 = f. create_eip1559 ( ) ;
117+ let tx_3 = f. create_eip1559 ( ) ;
118+ pool. add_transaction ( Arc :: new ( tx_1) , 0 ) ;
119+ pool. add_transaction ( Arc :: new ( tx_2) , 0 ) ;
120+ pool. add_transaction ( Arc :: new ( tx_3) , 0 ) ;
121+
122+ // Create iterator
123+ let mut iterator = BestFlashblocksTxs :: new ( BestPayloadTransactions :: new ( pool. best ( ) ) ) ;
124+ // ### First flashblock
125+ iterator. refresh_iterator ( BestPayloadTransactions :: new ( pool. best ( ) ) , 0 ) ;
126+ // Accept first tx
127+ let tx1 = iterator. next ( ( ) ) . unwrap ( ) ;
128+ // Invalidate second tx
129+ let tx2 = iterator. next ( ( ) ) . unwrap ( ) ;
130+ iterator. mark_invalid ( tx2. sender ( ) , tx2. nonce ( ) ) ;
131+ // Accept third tx
132+ let tx3 = iterator. next ( ( ) ) . unwrap ( ) ;
133+ // Check that it's empty
134+ assert ! ( iterator. next( ( ) ) . is_none( ) , "Iterator should be empty" ) ;
135+ // Mark transaction as commited
136+ iterator. mark_commited ( vec ! [ * tx1. hash( ) , * tx3. hash( ) ] ) ;
137+
138+ // ### Second flashblock
139+ // It should not return txs 1 and 3, but should return 2
140+ iterator. refresh_iterator ( BestPayloadTransactions :: new ( pool. best ( ) ) , 1 ) ;
141+ let tx2 = iterator. next ( ( ) ) . unwrap ( ) ;
142+ // Check that it's empty
143+ assert ! ( iterator. next( ( ) ) . is_none( ) , "Iterator should be empty" ) ;
144+ // Mark transaction as commited
145+ iterator. mark_commited ( vec ! [ * tx2. hash( ) ] ) ;
146+
147+ // ### Third flashblock
148+ iterator. refresh_iterator ( BestPayloadTransactions :: new ( pool. best ( ) ) , 2 ) ;
149+ // Check that it's empty
150+ assert ! ( iterator. next( ( ) ) . is_none( ) , "Iterator should be empty" ) ;
151+ }
123152
124- // Clear early_transactions from this sender with a greater nonce as
125- // these transactions now will not execute because there would be a
126- // nonce gap
127- self . early_transactions . retain ( |_, txs| {
128- txs. retain ( |tx| !( tx. sender ( ) == sender && tx. nonce ( ) > nonce) ) ;
129- !txs. is_empty ( )
130- } ) ;
153+ /// Test bundle cases
154+ /// We won't mark transactions as commited to test that boundaries are respected
155+ #[ test]
156+ fn test_bundle_case ( ) {
157+ let mut pool = PendingPool :: new ( CoinbaseTipOrdering :: < MockFbTransaction > :: default ( ) ) ;
158+ let mut f = MockFbTransactionFactory :: default ( ) ;
159+
160+ // Add 4 fb transaction
161+ let tx_1 = f. create_legacy_fb ( None , None ) ;
162+ let tx_1_hash = * tx_1. hash ( ) ;
163+ let tx_2 = f. create_legacy_fb ( None , Some ( 1 ) ) ;
164+ let tx_2_hash = * tx_2. hash ( ) ;
165+ let tx_3 = f. create_legacy_fb ( Some ( 1 ) , None ) ;
166+ let tx_3_hash = * tx_3. hash ( ) ;
167+ let tx_4 = f. create_legacy_fb ( Some ( 2 ) , Some ( 3 ) ) ;
168+ let tx_4_hash = * tx_4. hash ( ) ;
169+ pool. add_transaction ( Arc :: new ( tx_1) , 0 ) ;
170+ pool. add_transaction ( Arc :: new ( tx_2) , 0 ) ;
171+ pool. add_transaction ( Arc :: new ( tx_3) , 0 ) ;
172+ pool. add_transaction ( Arc :: new ( tx_4) , 0 ) ;
173+
174+ // Create iterator
175+ let mut iterator = BestFlashblocksTxs :: new ( BestPayloadTransactions :: new ( pool. best ( ) ) ) ;
176+ // ### First flashblock
177+ // should contain txs 1 and 2
178+ iterator. refresh_iterator ( BestPayloadTransactions :: new ( pool. best ( ) ) , 0 ) ;
179+ let tx1 = iterator. next ( ( ) ) . unwrap ( ) ;
180+ assert_eq ! ( tx1. hash( ) , & tx_1_hash) ;
181+ let tx2 = iterator. next ( ( ) ) . unwrap ( ) ;
182+ assert_eq ! ( tx2. hash( ) , & tx_2_hash) ;
183+ // Check that it's empty
184+ assert ! ( iterator. next( ( ) ) . is_none( ) , "Iterator should be empty" ) ;
185+
186+ // ### Second flashblock
187+ // should contain txs 1, 2, and 3
188+ iterator. refresh_iterator ( BestPayloadTransactions :: new ( pool. best ( ) ) , 1 ) ;
189+ let tx1 = iterator. next ( ( ) ) . unwrap ( ) ;
190+ assert_eq ! ( tx1. hash( ) , & tx_1_hash) ;
191+ let tx2 = iterator. next ( ( ) ) . unwrap ( ) ;
192+ assert_eq ! ( tx2. hash( ) , & tx_2_hash) ;
193+ let tx3 = iterator. next ( ( ) ) . unwrap ( ) ;
194+ assert_eq ! ( tx3. hash( ) , & tx_3_hash) ;
195+ // Check that it's empty
196+ assert ! ( iterator. next( ( ) ) . is_none( ) , "Iterator should be empty" ) ;
197+
198+ // ### Third flashblock
199+ // should contain txs 1, 3, and 4
200+ iterator. refresh_iterator ( BestPayloadTransactions :: new ( pool. best ( ) ) , 2 ) ;
201+ let tx1 = iterator. next ( ( ) ) . unwrap ( ) ;
202+ assert_eq ! ( tx1. hash( ) , & tx_1_hash) ;
203+ let tx3 = iterator. next ( ( ) ) . unwrap ( ) ;
204+ assert_eq ! ( tx3. hash( ) , & tx_3_hash) ;
205+ let tx4 = iterator. next ( ( ) ) . unwrap ( ) ;
206+ assert_eq ! ( tx4. hash( ) , & tx_4_hash) ;
207+ // Check that it's empty
208+ assert ! ( iterator. next( ( ) ) . is_none( ) , "Iterator should be empty" ) ;
209+
210+ // ### Forth flashblock
211+ // should contain txs 1, 3, and 4
212+ iterator. refresh_iterator ( BestPayloadTransactions :: new ( pool. best ( ) ) , 3 ) ;
213+ let tx1 = iterator. next ( ( ) ) . unwrap ( ) ;
214+ assert_eq ! ( tx1. hash( ) , & tx_1_hash) ;
215+ let tx3 = iterator. next ( ( ) ) . unwrap ( ) ;
216+ assert_eq ! ( tx3. hash( ) , & tx_3_hash) ;
217+ let tx4 = iterator. next ( ( ) ) . unwrap ( ) ;
218+ assert_eq ! ( tx4. hash( ) , & tx_4_hash) ;
219+ // Check that it's empty
220+ assert ! ( iterator. next( ( ) ) . is_none( ) , "Iterator should be empty" ) ;
221+
222+ // ### Fifth flashblock
223+ // should contain txs 1 and 3
224+ iterator. refresh_iterator ( BestPayloadTransactions :: new ( pool. best ( ) ) , 4 ) ;
225+ let tx1 = iterator. next ( ( ) ) . unwrap ( ) ;
226+ assert_eq ! ( tx1. hash( ) , & tx_1_hash) ;
227+ let tx3 = iterator. next ( ( ) ) . unwrap ( ) ;
228+ assert_eq ! ( tx3. hash( ) , & tx_3_hash) ;
229+ // Check that it's empty
230+ assert ! ( iterator. next( ( ) ) . is_none( ) , "Iterator should be empty" ) ;
131231 }
132232}
0 commit comments