@@ -239,8 +239,7 @@ impl ProposalBuilder {
239239 proposal. anchor_state_root = anchor_info. state_root ( ) ;
240240 }
241241
242- let bytes_length =
243- crate :: shared:: l2_tx_lists:: encode_and_compress ( & tx_list) ?. len ( ) as u64 ;
242+ let bytes_length = crate :: shared:: l2_tx_lists:: rlp_encode ( & tx_list) . len ( ) as u64 ;
244243
245244 let l2_draft_block = L2BlockV2Draft {
246245 prebuilt_tx_list : PreBuiltTxList {
@@ -251,6 +250,7 @@ impl ProposalBuilder {
251250 timestamp_sec : l2_block_timestamp_sec,
252251 gas_limit_without_anchor : gas_limit,
253252 } ;
253+
254254 if !self . can_consume_l2_block ( & l2_draft_block) {
255255 return Err ( anyhow:: anyhow!(
256256 "recover_from: block does not fit in proposal {} (adding {} bytes would exceed blob size limit). Reorg needed." ,
@@ -504,6 +504,7 @@ mod tests {
504504 use alloy:: primitives:: { B256 , Uint } ;
505505 use common:: l1:: slot_clock:: SlotClock ;
506506 use common:: metrics:: Metrics ;
507+ use rand:: Rng ;
507508
508509 const COINBASE : Address = Address :: ZERO ;
509510
@@ -535,6 +536,38 @@ mod tests {
535536 . expect ( "valid test tx json" )
536537 }
537538
539+ fn make_tx_with_size ( size : usize ) -> alloy:: rpc:: types:: Transaction {
540+ let mut bytes = vec ! [ 0_u8 ; size] ;
541+ rand:: rng ( ) . fill ( bytes. as_mut_slice ( ) ) ;
542+ let input = format ! ( "0x{}" , hex:: encode( bytes) ) ;
543+ serde_json:: from_str ( & format ! (
544+ r#"{{
545+ "blockHash":"0x0000000000000000000000000000000000000000000000000000000000000000",
546+ "blockNumber":"0x1",
547+ "from":"0x0000000000000000000000000000000000000001",
548+ "gas":"0x5208",
549+ "gasPrice":"0x1",
550+ "hash":"0x0000000000000000000000000000000000000000000000000000000000000001",
551+ "input":"{}",
552+ "nonce":"0x0",
553+ "to":"0x0000000000000000000000000000000000000002",
554+ "transactionIndex":"0x0",
555+ "value":"0x0",
556+ "type":"0x2",
557+ "accessList":[],
558+ "chainId":"0x1",
559+ "maxFeePerGas":"0x1",
560+ "maxPriorityFeePerGas":"0x0",
561+ "v":"0x0",
562+ "r":"0x0000000000000000000000000000000000000000000000000000000000000000",
563+ "s":"0x0000000000000000000000000000000000000000000000000000000000000000",
564+ "yParity":"0x0"
565+ }}"# ,
566+ input
567+ ) )
568+ . expect ( "valid test tx json" )
569+ }
570+
538571 fn make_config ( ) -> BatchBuilderConfig {
539572 BatchBuilderConfig {
540573 max_bytes_size_of_batch : 10_000 ,
@@ -559,6 +592,29 @@ mod tests {
559592 ProposalBuilder :: new ( config, slot_clock, metrics)
560593 }
561594
595+ fn make_recovery_stress_config ( ) -> BatchBuilderConfig {
596+ BatchBuilderConfig {
597+ max_bytes_size_of_batch : 10_000 ,
598+ max_blocks_per_batch : 100 ,
599+ l1_slot_duration_sec : 12 ,
600+ max_time_shift_between_blocks_sec : 255 ,
601+ max_anchor_height_offset : 64 ,
602+ default_coinbase : COINBASE ,
603+ preconf_min_txs : 3 ,
604+ preconf_max_skipped_l2_slots : 5 ,
605+ proposal_max_time_sec : 120 ,
606+ }
607+ }
608+
609+ fn build_recovery_txs_list (
610+ tx_per_block : usize ,
611+ tx_size : usize ,
612+ ) -> Vec < alloy:: rpc:: types:: Transaction > {
613+ ( 0 ..tx_per_block)
614+ . map ( |_| make_tx_with_size ( tx_size) )
615+ . collect ( )
616+ }
617+
562618 fn make_anchor ( id : u64 , timestamp_sec : u64 ) -> AnchorBlockInfo {
563619 AnchorBlockInfo :: new ( id, timestamp_sec, B256 :: ZERO , B256 :: ZERO )
564620 }
@@ -1003,6 +1059,99 @@ mod tests {
10031059
10041060 // --- Recovery ---
10051061
1062+ #[ tokio:: test]
1063+ async fn test_overfill_proposal ( ) {
1064+ const RECOVERABLE_BLOCKS_PER_PROPOSAL : u64 = 47 ;
1065+ const RECOVERY_TXS_PER_BLOCK : usize = 2 ;
1066+ const RECOVERY_TX_INPUT_BYTES : usize = 100 ;
1067+
1068+ let mut builder = make_builder_with_config ( make_recovery_stress_config ( ) ) ;
1069+
1070+ for block_id in 1 ..=RECOVERABLE_BLOCKS_PER_PROPOSAL {
1071+ let anchor = make_anchor ( 100 , 1000 ) ;
1072+ builder
1073+ . recover_from (
1074+ 1 ,
1075+ anchor,
1076+ COINBASE ,
1077+ build_recovery_txs_list ( RECOVERY_TXS_PER_BLOCK , RECOVERY_TX_INPUT_BYTES ) ,
1078+ 1000 + block_id,
1079+ 1_000_000 ,
1080+ false ,
1081+ )
1082+ . await
1083+ . expect ( "recovering a fitting block should succeed" ) ;
1084+ }
1085+
1086+ let anchor = make_anchor ( 100 , 1000 ) ;
1087+ let res = builder
1088+ . recover_from (
1089+ 1 ,
1090+ anchor,
1091+ COINBASE ,
1092+ build_recovery_txs_list ( RECOVERY_TXS_PER_BLOCK , RECOVERY_TX_INPUT_BYTES ) ,
1093+ 1000 + RECOVERABLE_BLOCKS_PER_PROPOSAL + 1 ,
1094+ 1_000_000 ,
1095+ false ,
1096+ )
1097+ . await ;
1098+ assert ! (
1099+ res. is_err( ) ,
1100+ "should not be able to recover when proposal is full"
1101+ ) ;
1102+ assert_eq ! ( builder. get_current_proposal_id( ) , Some ( 1 ) ) ;
1103+ assert_eq ! (
1104+ builder
1105+ . current_proposal
1106+ . as_ref( )
1107+ . expect( "proposal should still exist" )
1108+ . l2_blocks
1109+ . len( ) ,
1110+ RECOVERABLE_BLOCKS_PER_PROPOSAL as usize
1111+ ) ;
1112+ }
1113+
1114+ #[ tokio:: test]
1115+ async fn test_recover_three_full_proposals ( ) {
1116+ const PROPOSALS : u64 = 3 ;
1117+ const RECOVERABLE_BLOCKS_PER_PROPOSAL : u64 = 47 ;
1118+ const RECOVERY_TXS_PER_BLOCK : usize = 2 ;
1119+ const RECOVERY_TX_INPUT_BYTES : usize = 100 ;
1120+
1121+ let mut builder = make_builder_with_config ( make_recovery_stress_config ( ) ) ;
1122+
1123+ for proposal_id in 1 ..=PROPOSALS {
1124+ let timestamp_base = 1000 * proposal_id;
1125+ for block_id in 1 ..=RECOVERABLE_BLOCKS_PER_PROPOSAL {
1126+ let anchor = make_anchor ( 100 * proposal_id, timestamp_base) ;
1127+ builder
1128+ . recover_from (
1129+ proposal_id,
1130+ anchor,
1131+ COINBASE ,
1132+ build_recovery_txs_list ( RECOVERY_TXS_PER_BLOCK , RECOVERY_TX_INPUT_BYTES ) ,
1133+ timestamp_base + block_id,
1134+ 1_000_000 ,
1135+ false ,
1136+ )
1137+ . await
1138+ . expect ( "recovering a fitting block should succeed" ) ;
1139+ }
1140+ }
1141+
1142+ builder. finalize_current_proposal ( ) ;
1143+ assert_eq ! ( builder. get_current_proposal_id( ) , None ) ;
1144+ assert_eq ! ( builder. queue. len( ) , PROPOSALS ) ;
1145+ builder. queue . take_all ( ) . iter_mut ( ) . for_each ( |proposal| {
1146+ proposal. compress ( ) ;
1147+ assert_eq ! (
1148+ proposal. l2_blocks. len( ) ,
1149+ RECOVERABLE_BLOCKS_PER_PROPOSAL as usize
1150+ ) ;
1151+ assert ! ( proposal. total_bytes <= builder. config. max_bytes_size_of_batch as u64 ) ;
1152+ } ) ;
1153+ }
1154+
10061155 #[ tokio:: test]
10071156 async fn test_recover_from_creates_new_proposal ( ) {
10081157 let mut builder = make_builder ( ) ;
0 commit comments