11use {
22 crate :: {
33 Flashblocks ,
4- tests:: { assert_has_sequencer_tx, random_valid_bundle} ,
4+ bundle:: FlashblocksBundle ,
5+ tests:: assert_has_sequencer_tx,
56 } ,
7+ jsonrpsee:: core:: ClientError ,
8+ rand:: { Rng , rng} ,
69 rblib:: {
7- alloy:: { consensus:: Transaction , primitives:: U256 } ,
10+ alloy:: {
11+ consensus:: Transaction ,
12+ network:: { TransactionBuilder , TxSignerSync } ,
13+ optimism:: { consensus:: OpTxEnvelope , rpc_types:: OpTransactionRequest } ,
14+ primitives:: { Address , U256 } ,
15+ signers:: local:: PrivateKeySigner ,
16+ } ,
817 pool:: { BundleResult , BundlesApiClient } ,
918 prelude:: * ,
19+ reth:: {
20+ core:: primitives:: SignedTransaction ,
21+ optimism:: primitives:: OpTransactionSigned ,
22+ primitives:: Recovered ,
23+ } ,
1024 test_utils:: * ,
1125 } ,
12- tracing:: debug,
1326} ;
1427
28+ macro_rules! assert_ineligible {
29+ ( $result: expr) => {
30+ let result = $result;
31+ assert!(
32+ result. is_err( ) ,
33+ "Expected error for this bundle, got {result:?}"
34+ ) ;
35+
36+ let Err ( ClientError :: Call ( error) ) = result else {
37+ panic!( "Expected Call error, got {result:?}" ) ;
38+ } ;
39+
40+ assert_eq!(
41+ error. code( ) ,
42+ jsonrpsee:: types:: ErrorCode :: InvalidParams . code( )
43+ ) ;
44+
45+ assert_eq!( error. message( ) , "bundle is ineligible for inclusion" ) ;
46+ } ;
47+ }
48+
49+ pub fn transfer_tx (
50+ signer : & PrivateKeySigner ,
51+ nonce : u64 ,
52+ value : U256 ,
53+ ) -> Recovered < OpTxEnvelope > {
54+ let mut tx = OpTransactionRequest :: default ( )
55+ . with_nonce ( nonce)
56+ . with_to ( Address :: random ( ) )
57+ . value ( value)
58+ . with_gas_price ( 1_000_000_000 )
59+ . with_gas_limit ( 21_000 )
60+ . with_max_priority_fee_per_gas ( 1_000_000 )
61+ . with_max_fee_per_gas ( 2_000_000 )
62+ . build_unsigned ( )
63+ . expect ( "valid transaction request" ) ;
64+
65+ let sig = signer
66+ . sign_transaction_sync ( & mut tx)
67+ . expect ( "signing should succeed" ) ;
68+
69+ OpTransactionSigned :: new_unhashed ( tx, sig) //
70+ . with_signer ( signer. address ( ) )
71+ }
72+
73+ pub fn transfer_tx_compact (
74+ signer : u32 ,
75+ nonce : u64 ,
76+ value : u64 ,
77+ ) -> Recovered < OpTxEnvelope > {
78+ let signer = FundedAccounts :: signer ( signer) ;
79+ transfer_tx ( & signer, nonce, U256 :: from ( value) )
80+ }
81+
82+ /// Will generate a random bundle with a given number of valid transactions.
83+ /// Transaction will be sending `1_000_000` + index wei to a random address.
84+ pub fn random_valid_bundle ( tx_count : usize ) -> FlashblocksBundle {
85+ random_bundle_with_reverts ( tx_count, 0 )
86+ }
87+
88+ /// Non-reverting transactions amount value is `1_000_000` + index wei.
89+ /// Reverting transactions amount value is `2_000_000` + index wei.
90+ pub fn random_bundle_with_reverts (
91+ non_reverting : usize ,
92+ reverting : usize ,
93+ ) -> FlashblocksBundle {
94+ const SIGNERS_COUNT : usize = FundedAccounts :: len ( ) ;
95+ let mut txs = Vec :: new ( ) ;
96+ let mut nonces = [ 0u64 ; SIGNERS_COUNT ] ;
97+
98+ // first valid transactions
99+ for i in 0 ..non_reverting {
100+ let signer = rng ( ) . random_range ( 0 ..SIGNERS_COUNT ) ;
101+ let nonce = nonces[ signer] ;
102+ let amount = 1_000_000 + i as u64 ;
103+
104+ #[ expect( clippy:: cast_possible_truncation) ]
105+ let tx = transfer_tx_compact ( signer as u32 , nonce, amount) ;
106+ txs. push ( tx) ;
107+ nonces[ signer] += 1 ;
108+ }
109+
110+ // then reverting transactions
111+ for i in 0 ..reverting {
112+ let signer = rng ( ) . random_range ( 0 ..SIGNERS_COUNT ) ;
113+ let nonce = nonces[ signer] ;
114+ nonces[ signer] += 1 ;
115+
116+ #[ expect( clippy:: cast_possible_truncation) ]
117+ let signer = FundedAccounts :: signer ( signer as u32 ) ;
118+ let amount = 2_000_000 + i as u64 ;
119+ let mut tx = OpTransactionRequest :: default ( )
120+ . with_nonce ( nonce)
121+ . value ( U256 :: from ( amount) )
122+ . reverting ( )
123+ . with_gas_price ( 1_000_000_000 )
124+ . with_gas_limit ( 100_000 )
125+ . with_max_priority_fee_per_gas ( 1_000_000 )
126+ . with_max_fee_per_gas ( 2_000_000 )
127+ . build_unsigned ( )
128+ . expect ( "valid transaction request" ) ;
129+
130+ let sig = signer
131+ . sign_transaction_sync ( & mut tx)
132+ . expect ( "signing should succeed" ) ;
133+
134+ let tx = OpTransactionSigned :: new_unhashed ( tx, sig) //
135+ . with_signer ( signer. address ( ) ) ;
136+ txs. push ( tx) ;
137+ }
138+
139+ FlashblocksBundle :: with_transactions ( txs)
140+ }
141+
15142#[ tokio:: test]
16- async fn one_valid_tx_included ( ) -> eyre:: Result < ( ) > {
143+ async fn empty_bundle_rejected ( ) -> eyre:: Result < ( ) > {
144+ let ( node, _) = Flashblocks :: test_node ( ) . await ?;
145+
146+ let empty_bundle = FlashblocksBundle :: with_transactions ( vec ! [ ] ) ;
147+ let result = BundlesApiClient :: < Flashblocks > :: send_bundle (
148+ & node. rpc_client ( ) . await ?,
149+ empty_bundle,
150+ )
151+ . await ;
152+
153+ assert_ineligible ! ( result) ;
154+
155+ Ok ( ( ) )
156+ }
157+
158+ /// This bundle should be rejected by because we only support bundles with one
159+ /// transaction
160+ #[ tokio:: test]
161+ async fn bundle_with_two_txs_rejected ( ) -> eyre:: Result < ( ) > {
162+ let ( node, _) = Flashblocks :: test_node ( ) . await ?;
163+
164+ let bundle_with_two_txs = random_valid_bundle ( 2 ) ;
165+
166+ let result = BundlesApiClient :: < Flashblocks > :: send_bundle (
167+ & node. rpc_client ( ) . await ?,
168+ bundle_with_two_txs,
169+ )
170+ . await ;
171+
172+ assert_ineligible ! ( result) ;
173+
174+ Ok ( ( ) )
175+ }
176+
177+ #[ tokio:: test]
178+ async fn valid_tx_included ( ) -> eyre:: Result < ( ) > {
17179 let ( node, _) = Flashblocks :: test_node ( ) . await ?;
18180
19181 let bundle_with_one_tx = random_valid_bundle ( 1 ) ;
@@ -28,7 +190,6 @@ async fn one_valid_tx_included() -> eyre::Result<()> {
28190 assert_eq ! ( result, BundleResult { bundle_hash } ) ;
29191
30192 let block = node. next_block ( ) . await ?;
31- debug ! ( "Built block: {block:#?}" ) ;
32193
33194 assert_eq ! ( block. number( ) , 1 ) ;
34195 assert_has_sequencer_tx ! ( & block) ;
@@ -39,38 +200,101 @@ async fn one_valid_tx_included() -> eyre::Result<()> {
39200}
40201
41202#[ tokio:: test]
42- async fn two_valid_txs_included ( ) -> eyre:: Result < ( ) > {
203+ async fn reverted_tx_not_included ( ) -> eyre:: Result < ( ) > {
43204 let ( node, _) = Flashblocks :: test_node ( ) . await ?;
44205
45- let bundle_with_two_txs = random_valid_bundle ( 2 ) ;
46- let bundle_hash = bundle_with_two_txs. hash ( ) ;
206+ let bundle_with_reverts = random_bundle_with_reverts ( 0 , 1 ) ;
47207
48- let result = BundlesApiClient :: < Flashblocks > :: send_bundle (
208+ BundlesApiClient :: < Flashblocks > :: send_bundle (
49209 & node. rpc_client ( ) . await ?,
50- bundle_with_two_txs ,
210+ bundle_with_reverts . clone ( ) ,
51211 )
52212 . await ?;
53213
54- assert_eq ! ( result, BundleResult { bundle_hash } ) ;
55-
56214 let block = node. next_block ( ) . await ?;
57- debug ! ( "Built block: {block:#?}" ) ;
58215
59216 assert_eq ! ( block. number( ) , 1 ) ;
217+ assert_eq ! ( block. tx_count( ) , 1 ) ; // only sequencer deposit tx
218+
60219 assert_has_sequencer_tx ! ( & block) ;
61- assert_eq ! ( block. tx_count( ) , 3 ) ; // sequencer deposit tx + 2 bundle txs
62- assert_eq ! ( block. tx( 1 ) . unwrap( ) . value( ) , U256 :: from( 1_000_000 ) ) ;
63- assert_eq ! ( block. tx( 2 ) . unwrap( ) . value( ) , U256 :: from( 1_000_001 ) ) ;
64220
65221 Ok ( ( ) )
66222}
67223
224+ /// Bundles that will never be eligible for inclusion in any future block
225+ /// should be rejected by the RPC before making it to the orders pool.
226+ #[ tokio:: test]
227+ async fn max_block_number_in_past ( ) -> eyre:: Result < ( ) > {
228+ let ( node, _) = Flashblocks :: test_node ( ) . await ?;
229+
230+ let block = node. next_block ( ) . await ?;
231+ assert_eq ! ( block. number( ) , 1 ) ;
232+
233+ let block = node. next_block ( ) . await ?;
234+ assert_eq ! ( block. number( ) , 2 ) ;
235+
236+ let mut bundle = random_valid_bundle ( 1 ) ;
237+ bundle. max_block_number = Some ( 1 ) ;
238+
239+ let result = BundlesApiClient :: < Flashblocks > :: send_bundle (
240+ & node. rpc_client ( ) . await ?,
241+ bundle,
242+ )
243+ . await ;
244+
245+ assert_ineligible ! ( result) ;
246+
247+ Ok ( ( ) )
248+ }
249+
250+ /// This bundle should be rejected because its `max_timestamp` is in the past
251+ /// and it will never be eligible for inclusion in any future block.
68252#[ tokio:: test]
69- async fn min_block_timestamp_constraint ( ) -> eyre:: Result < ( ) > {
253+ async fn max_block_timestamp_in_past ( ) -> eyre:: Result < ( ) > {
254+ // node at genesis, block 0
255+ let ( node, _) = Flashblocks :: test_node ( ) . await ?;
256+ let genesis_timestamp = node. config ( ) . chain . genesis_timestamp ( ) ;
257+ let mut bundle = random_valid_bundle ( 1 ) ;
258+ bundle. max_timestamp = Some ( genesis_timestamp. saturating_sub ( 1 ) ) ;
259+
260+ let result = BundlesApiClient :: < Flashblocks > :: send_bundle (
261+ & node. rpc_client ( ) . await ?,
262+ bundle,
263+ )
264+ . await ;
265+
266+ assert_ineligible ! ( result) ;
267+
268+ Ok ( ( ) )
269+ }
270+
271+ #[ tokio:: test]
272+ async fn min_block_greater_than_max_block ( ) -> eyre:: Result < ( ) > {
273+ // node at genesis, block 0
274+ let ( node, _) = Flashblocks :: test_node ( ) . await ?;
275+ let mut bundle = random_valid_bundle ( 1 ) ;
276+ bundle. min_block_number = Some ( 2 ) ;
277+ bundle. max_block_number = Some ( 1 ) ;
278+
279+ let result = BundlesApiClient :: < Flashblocks > :: send_bundle (
280+ & node. rpc_client ( ) . await ?,
281+ bundle,
282+ )
283+ . await ;
284+
285+ assert_ineligible ! ( result) ;
286+
287+ Ok ( ( ) )
288+ }
289+
290+ /// Test that a bundle with the `min_block_number` param set to a future block
291+ /// isn't included until that block.
292+ #[ tokio:: test]
293+ async fn min_block_number_in_future ( ) -> eyre:: Result < ( ) > {
70294 let ( node, _) = Flashblocks :: test_node ( ) . await ?;
71295
72296 let mut bundle_with_one_tx = random_valid_bundle ( 1 ) ;
73- bundle_with_one_tx. min_block_number = Some ( 3 ) ;
297+ bundle_with_one_tx. min_block_number = Some ( 2 ) ;
74298 let bundle_hash = bundle_with_one_tx. hash ( ) ;
75299 let txhash = bundle_with_one_tx. transactions ( ) [ 0 ] . tx_hash ( ) ;
76300
@@ -89,15 +313,40 @@ async fn min_block_timestamp_constraint() -> eyre::Result<()> {
89313
90314 let block = node. next_block ( ) . await ?; // block 2
91315 assert_eq ! ( block. number( ) , 2 ) ;
92- assert_eq ! ( block. tx_count( ) , 1 ) ; // only sequencer tx
93- assert_has_sequencer_tx ! ( & block) ;
94-
95- let block = node. next_block ( ) . await ?; // block 3
96- assert_eq ! ( block. number( ) , 3 ) ;
97- assert_eq ! ( block. tx_count( ) , 2 ) ; // sequencer tx + 1 bundle tx
316+ assert_eq ! ( block. tx_count( ) , 2 ) ; // sequencer tx + bundle tx
98317 assert_has_sequencer_tx ! ( & block) ;
99318
100319 assert ! ( block. includes( txhash) ) ;
101320
102321 Ok ( ( ) )
103322}
323+
324+ #[ tokio:: test]
325+ async fn when_disabled_reverted_txs_are_included ( ) -> eyre:: Result < ( ) > {
326+ let ( node, _) = Flashblocks :: test_node_with_revert_protection_off ( ) . await ?;
327+
328+ // create a bundle with one valid and one reverting tx
329+ let mut bundle_with_reverts = random_bundle_with_reverts ( 0 , 1 ) ;
330+ let txs = bundle_with_reverts. transactions ( ) . to_vec ( ) ;
331+
332+ // mark the transaction (reverting) in the bundle as allowed to revert
333+ // and optional (i.e. it can be removed from the bundle)
334+ bundle_with_reverts. reverting_tx_hashes = vec ! [ txs[ 0 ] . tx_hash( ) ] ;
335+ bundle_with_reverts. dropping_tx_hashes = vec ! [ txs[ 0 ] . tx_hash( ) ] ;
336+
337+ BundlesApiClient :: < Flashblocks > :: send_bundle (
338+ & node. rpc_client ( ) . await ?,
339+ bundle_with_reverts. clone ( ) ,
340+ )
341+ . await ?;
342+
343+ let block = node. next_block ( ) . await ?;
344+
345+ assert_eq ! ( block. number( ) , 1 ) ;
346+ assert_eq ! ( block. tx_count( ) , 2 ) ;
347+
348+ assert_has_sequencer_tx ! ( & block) ;
349+ assert ! ( block. includes( txs[ 0 ] . tx_hash( ) ) ) ;
350+
351+ Ok ( ( ) )
352+ }
0 commit comments