@@ -1115,22 +1115,69 @@ impl Batcher {
11151115 } ;
11161116
11171117 let original_max_fee = entry. nonced_verification_data . max_fee ;
1118- if original_max_fee > replacement_max_fee {
1118+ // Require 10% fee increase to prevent DoS attacks. While this could theoretically overflow,
1119+ // it would require an attacker to have an impractical amount of Ethereum to reach U256::MAX
1120+ let min_required_fee = original_max_fee + ( original_max_fee / U256 :: from ( 10 ) ) ; // 10% increase (1.1x)
1121+ if replacement_max_fee < min_required_fee {
11191122 drop ( batch_state_guard) ;
11201123 drop ( user_state_guard) ;
1121- warn ! ( "Invalid replacement message for address {addr}, had max fee : {original_max_fee:?}, received fee : {replacement_max_fee:?}" ) ;
1124+ info ! ( "Replacement message fee increase too small for address {addr}. Original : {original_max_fee:?}, received: {replacement_max_fee:?}, minimum required: {min_required_fee :?}" ) ;
11221125 send_message (
11231126 ws_conn_sink. clone ( ) ,
11241127 SubmitProofResponseMessage :: InvalidReplacementMessage ,
11251128 )
11261129 . await ;
11271130 self . metrics
1128- . user_error ( & [ "invalid_replacement_message " , "" ] ) ;
1131+ . user_error ( & [ "insufficient_fee_increase " , "" ] ) ;
11291132 return ;
11301133 }
11311134
11321135 info ! ( "Replacing message for address {addr} with nonce {nonce} and max fee {replacement_max_fee}" ) ;
11331136
1137+ // When pre-verification is enabled, verify the replacement proof
1138+ if self . pre_verification_is_enabled {
1139+ let verification_data = & nonced_verification_data. verification_data ;
1140+ if self
1141+ . is_verifier_disabled ( verification_data. proving_system )
1142+ . await
1143+ {
1144+ drop ( batch_state_guard) ;
1145+ drop ( user_state_guard) ;
1146+ warn ! (
1147+ "Verifier for proving system {} is disabled for replacement message" ,
1148+ verification_data. proving_system
1149+ ) ;
1150+ send_message (
1151+ ws_conn_sink. clone ( ) ,
1152+ SubmitProofResponseMessage :: InvalidProof ( ProofInvalidReason :: DisabledVerifier (
1153+ verification_data. proving_system ,
1154+ ) ) ,
1155+ )
1156+ . await ;
1157+ self . metrics . user_error ( & [
1158+ "disabled_verifier" ,
1159+ & format ! ( "{}" , verification_data. proving_system) ,
1160+ ] ) ;
1161+ return ;
1162+ }
1163+
1164+ if !zk_utils:: verify ( verification_data) . await {
1165+ drop ( batch_state_guard) ;
1166+ drop ( user_state_guard) ;
1167+ error ! ( "Invalid replacement proof detected. Verification failed" ) ;
1168+ send_message (
1169+ ws_conn_sink. clone ( ) ,
1170+ SubmitProofResponseMessage :: InvalidProof ( ProofInvalidReason :: RejectedProof ) ,
1171+ )
1172+ . await ;
1173+ self . metrics . user_error ( & [
1174+ "rejected_proof" ,
1175+ & format ! ( "{}" , verification_data. proving_system) ,
1176+ ] ) ;
1177+ return ;
1178+ }
1179+ }
1180+
11341181 // The replacement entry is built from the old entry and validated for then to be replaced
11351182 let mut replacement_entry = entry. clone ( ) ;
11361183 replacement_entry. signature = signature;
@@ -1157,7 +1204,8 @@ impl Batcher {
11571204
11581205 replacement_entry. messaging_sink = Some ( ws_conn_sink. clone ( ) ) ;
11591206 if !batch_state_guard. replacement_entry_is_valid ( & replacement_entry) {
1160- std:: mem:: drop ( batch_state_guard) ;
1207+ drop ( batch_state_guard) ;
1208+ drop ( user_state_guard) ;
11611209 warn ! ( "Invalid replacement message" ) ;
11621210 send_message (
11631211 ws_conn_sink. clone ( ) ,
0 commit comments