@@ -4,7 +4,7 @@ use crate::{
4
4
utils:: extract_signature_components,
5
5
} ;
6
6
use alloy:: {
7
- consensus:: { SimpleCoder , Transaction , constants :: GWEI_TO_WEI } ,
7
+ consensus:: SimpleCoder ,
8
8
eips:: BlockNumberOrTag ,
9
9
network:: { TransactionBuilder , TransactionBuilder4844 } ,
10
10
primitives:: { FixedBytes , TxHash , U256 } ,
@@ -13,7 +13,7 @@ use alloy::{
13
13
sol_types:: { SolCall , SolError } ,
14
14
transports:: TransportError ,
15
15
} ;
16
- use eyre:: { bail, eyre } ;
16
+ use eyre:: bail;
17
17
use init4_bin_base:: deps:: {
18
18
metrics:: { counter, histogram} ,
19
19
tracing:: { self , Instrument , debug, debug_span, error, info, instrument, warn} ,
@@ -101,21 +101,11 @@ impl SubmitTask {
101
101
let data = submitCall { fills, header, v, r, s } . abi_encode ( ) ;
102
102
103
103
let sidecar = block. encode_blob :: < SimpleCoder > ( ) . build ( ) ?;
104
- Ok ( TransactionRequest :: default ( )
105
- . with_blob_sidecar ( sidecar)
106
- . with_input ( data)
107
- . with_max_priority_fee_per_gas ( ( GWEI_TO_WEI * 16 ) as u128 ) )
108
- }
109
104
110
- /// Returns the next host block height.
111
- async fn next_host_block_height ( & self ) -> eyre:: Result < u64 > {
112
- let result = self . provider ( ) . get_block_number ( ) . await ?;
113
- let next = result. checked_add ( 1 ) . ok_or_else ( || eyre ! ( "next host block height overflow" ) ) ?;
114
- debug ! ( next, "next host block height" ) ;
115
- Ok ( next)
105
+ Ok ( TransactionRequest :: default ( ) . with_blob_sidecar ( sidecar) . with_input ( data) )
116
106
}
117
107
118
- /// Prepares and then sends the EIP-4844 transaction with a sidecar encoded with a rollup block to the network.
108
+ /// Prepares and sends the EIP-4844 transaction with a sidecar encoded with a rollup block to the network.
119
109
async fn submit_transaction (
120
110
& self ,
121
111
retry_count : usize ,
@@ -140,10 +130,7 @@ impl SubmitTask {
140
130
141
131
// Simulate the transaction with a call to the host provider
142
132
if let Some ( maybe_error) = self . sim_with_call ( & tx) . await {
143
- warn ! (
144
- error = ?maybe_error,
145
- "error in transaction simulation"
146
- ) ;
133
+ warn ! ( error = ?maybe_error, "error in transaction simulation" ) ;
147
134
if let Err ( e) = maybe_error {
148
135
return Err ( e) ;
149
136
}
@@ -160,19 +147,13 @@ impl SubmitTask {
160
147
if let Err ( TransportError :: ErrorResp ( e) ) =
161
148
self . provider ( ) . call ( tx. clone ( ) ) . block ( BlockNumberOrTag :: Pending . into ( ) ) . await
162
149
{
163
- error ! (
164
- code = e. code,
165
- message = %e. message,
166
- data = ?e. data,
167
- "error in transaction submission"
168
- ) ;
169
-
150
+ // NB: These errors are all handled the same way but are logged for debugging purposes
170
151
if e. as_revert_data ( )
171
152
. map ( |data| data. starts_with ( & IncorrectHostBlock :: SELECTOR ) )
172
153
. unwrap_or_default ( )
173
154
{
174
155
debug ! ( %e, "incorrect host block" ) ;
175
- return Some ( Ok ( ControlFlow :: Retry ) ) ;
156
+ return Some ( Ok ( ControlFlow :: Skip ) ) ;
176
157
}
177
158
178
159
if e. as_revert_data ( )
@@ -191,6 +172,12 @@ impl SubmitTask {
191
172
return Some ( Ok ( ControlFlow :: Skip ) ) ;
192
173
}
193
174
175
+ error ! (
176
+ code = e. code,
177
+ message = %e. message,
178
+ data = ?e. data,
179
+ "unknown error in host transaction simulation call"
180
+ ) ;
194
181
return Some ( Ok ( ControlFlow :: Skip ) ) ;
195
182
}
196
183
@@ -207,22 +194,14 @@ impl SubmitTask {
207
194
) -> Result < TransactionRequest , eyre:: Error > {
208
195
// TODO: ENG-1082 Implement fills
209
196
let fills = vec ! [ ] ;
210
-
211
197
// Extract the signature components from the response
212
198
let ( v, r, s) = extract_signature_components ( & resp. sig ) ;
213
199
214
- // Bump gas with each retry to replace the previous
215
- // transaction while maintaining the same nonce
216
- // TODO: Clean this up if this works
217
- let gas_coefficient: u64 = ( 15 * ( retry_count + 1 ) ) . try_into ( ) . unwrap ( ) ;
218
- let gas_limit: u64 = 1_500_000 + ( gas_coefficient * 1_000_000 ) ;
219
- let max_priority_fee_per_gas: u128 = ( retry_count as u128 ) ;
220
- debug ! (
221
- retry_count,
222
- gas_coefficient, gas_limit, max_priority_fee_per_gas, "calculated gas limit"
223
- ) ;
200
+ // Calculate gas limits based on retry attempts
201
+ let ( max_fee_per_gas, max_priority_fee_per_gas, max_fee_per_blob_gas) =
202
+ calculate_gas_limits ( retry_count) ;
224
203
225
- // manually retrieve nonce
204
+ // manually retrieve nonce // TODO: Maybe this should be done in Env task and passed through elsewhere
226
205
let nonce =
227
206
self . provider ( ) . get_transaction_count ( self . provider ( ) . default_signer_address ( ) ) . await ?;
228
207
debug ! ( nonce, "assigned nonce" ) ;
@@ -240,13 +219,12 @@ impl SubmitTask {
240
219
// Create a blob transaction with the blob header and signature values and return it
241
220
let tx = self
242
221
. build_blob_tx ( fills, header, v, r, s, block) ?
243
- . with_from ( self . provider ( ) . default_signer_address ( ) )
244
222
. with_to ( self . config . builder_helper_address )
245
- . with_gas_limit ( gas_limit )
223
+ . with_max_fee_per_gas ( max_fee_per_gas )
246
224
. with_max_priority_fee_per_gas ( max_priority_fee_per_gas)
225
+ . with_max_fee_per_blob_gas ( max_fee_per_blob_gas)
247
226
. with_nonce ( nonce) ;
248
227
249
- debug ! ( ?tx, "prepared transaction request" ) ;
250
228
Ok ( tx)
251
229
}
252
230
@@ -257,23 +235,16 @@ impl SubmitTask {
257
235
resp : & SignResponse ,
258
236
tx : TransactionRequest ,
259
237
) -> Result < ControlFlow , eyre:: Error > {
260
- debug ! (
261
- host_block_number = %resp. req. host_block_number,
262
- gas_limit = %resp. req. gas_limit,
263
- nonce = ?tx. nonce,
264
- "sending transaction to network"
265
- ) ;
266
-
267
238
// assign the nonce and fill the rest of the values
268
239
let SendableTx :: Envelope ( tx) = self . provider ( ) . fill ( tx) . await ? else {
269
240
bail ! ( "failed to fill transaction" )
270
241
} ;
271
- debug ! ( tx_hash = %tx . tx_hash ( ) , nonce = ?tx. nonce ( ) , gas_limit = ?tx . gas_limit ( ) , blob_gas_used = ?tx . blob_gas_used ( ) , "filled blob transaction" ) ;
242
+ debug ! ( tx_hash = ?tx. hash ( ) , host_block_number = %resp . req . host_block_number , "sending transaction to network " ) ;
272
243
273
244
// send the tx via the primary host_provider
274
245
let fut = spawn_provider_send ! ( self . provider( ) , & tx) ;
275
246
276
- // spawn send_tx futures for all additional broadcast host_providers
247
+ // spawn send_tx futures on retry attempts for all additional broadcast host_providers
277
248
for host_provider in self . config . connect_additional_broadcast ( ) {
278
249
spawn_provider_send ! ( & host_provider, & tx) ;
279
250
}
@@ -283,16 +254,19 @@ impl SubmitTask {
283
254
error ! ( "receipts task gone" ) ;
284
255
}
285
256
286
- // question mark unwraps join error, which would be an internal panic
287
- // then if let checks for rpc error
288
257
if let Err ( e) = fut. await ? {
289
- error ! ( error = %e, "Primary tx broadcast failed. Skipping transaction." ) ;
258
+ // Detect and handle transaction underprice errors
259
+ if matches ! ( e, TransportError :: ErrorResp ( ref err) if err. code == -32000 && err. message. contains( "replacement transaction underpriced" ) )
260
+ {
261
+ debug ! ( ?tx, "underpriced transaction error - retrying tx with gas bump" ) ;
262
+ return Ok ( ControlFlow :: Retry ) ;
263
+ }
264
+
265
+ // Unknown error, log and skip
266
+ error ! ( error = %e, "Primary tx broadcast failed" ) ;
290
267
return Ok ( ControlFlow :: Skip ) ;
291
268
}
292
269
293
- // Okay so the code gets all the way to this log
294
- // but we don't see the tx hash in the logs or in the explorer,
295
- // not even as a failed TX, just not at all.
296
270
info ! (
297
271
tx_hash = %tx. tx_hash( ) ,
298
272
ru_chain_id = %resp. req. ru_chain_id,
@@ -341,54 +315,38 @@ impl SubmitTask {
341
315
342
316
// Retry loop
343
317
let result = loop {
318
+ // Log the retry attempt
344
319
let span = debug_span ! ( "SubmitTask::retrying_handle_inbound" , retries) ;
345
320
346
- let inbound_result = match self
347
- . handle_inbound ( retries, block)
348
- . instrument ( span. clone ( ) )
349
- . await
350
- {
351
- Ok ( control_flow) => {
352
- debug ! ( ?control_flow, retries, "successfully handled inbound block" ) ;
353
- control_flow
354
- }
355
- Err ( err) => {
356
- // Log the retry attempt
357
- retries += 1 ;
358
-
359
- // Delay until next slot if we get a 403 error
360
- if err. to_string ( ) . contains ( "403 Forbidden" ) {
361
- let ( slot_number, _, _) = self . calculate_slot_window ( ) ?;
362
- debug ! ( slot_number, ?block, "403 detected - not assigned to slot" ) ;
363
- return Ok ( ControlFlow :: Skip ) ;
364
- } else {
365
- error ! ( error = %err, "error handling inbound block" ) ;
321
+ let inbound_result =
322
+ match self . handle_inbound ( retries, block) . instrument ( span. clone ( ) ) . await {
323
+ Ok ( control_flow) => control_flow,
324
+ Err ( err) => {
325
+ // Delay until next slot if we get a 403 error
326
+ if err. to_string ( ) . contains ( "403 Forbidden" ) {
327
+ let ( slot_number, _, _) = self . calculate_slot_window ( ) ?;
328
+ debug ! ( slot_number, "403 detected - skipping slot" ) ;
329
+ return Ok ( ControlFlow :: Skip ) ;
330
+ } else {
331
+ error ! ( error = %err, "error handling inbound block" ) ;
332
+ }
333
+
334
+ ControlFlow :: Retry
366
335
}
367
-
368
- ControlFlow :: Retry
369
- }
370
- } ;
336
+ } ;
371
337
372
338
let guard = span. entered ( ) ;
373
339
374
340
match inbound_result {
375
341
ControlFlow :: Retry => {
342
+ retries += 1 ;
376
343
if retries > retry_limit {
377
344
counter ! ( "builder.building_too_many_retries" ) . increment ( 1 ) ;
378
345
debug ! ( "retries exceeded - skipping block" ) ;
379
346
return Ok ( ControlFlow :: Skip ) ;
380
347
}
381
348
drop ( guard) ;
382
-
383
- // Detect a slot change and break out of the loop in that case too
384
- let ( this_slot, start, end) = self . calculate_slot_window ( ) ?;
385
- if this_slot != current_slot {
386
- debug ! ( "slot changed - skipping block" ) ;
387
- break inbound_result;
388
- }
389
-
390
- // Otherwise retry the block
391
- debug ! ( retries, this_slot, start, end, "retrying block" ) ;
349
+ debug ! ( retries, start, end, "retrying block" ) ;
392
350
continue ;
393
351
}
394
352
ControlFlow :: Skip => {
@@ -424,6 +382,12 @@ impl SubmitTask {
424
382
let now = std:: time:: SystemTime :: now ( ) ;
425
383
now. duration_since ( UNIX_EPOCH ) . unwrap ( ) . as_secs ( )
426
384
}
385
+
386
+ /// Returns the next host block height.
387
+ async fn next_host_block_height ( & self ) -> eyre:: Result < u64 > {
388
+ let block_num = self . provider ( ) . get_block_number ( ) . await ?;
389
+ Ok ( block_num + 1 )
390
+ }
427
391
428
392
/// Task future for the submit task
429
393
/// NB: This task assumes that the simulator will only send it blocks for
@@ -471,3 +435,27 @@ impl SubmitTask {
471
435
( sender, handle)
472
436
}
473
437
}
438
+
439
+ fn calculate_gas_limits ( retry_count : usize ) -> ( u128 , u128 , u128 ) {
440
+ let base_fee_per_gas: u128 = 100_000_000_000 ;
441
+ let base_priority_fee_per_gas: u128 = 2_000_000_000 ;
442
+ let base_fee_per_blob_gas: u128 = 1_000_000_000 ;
443
+
444
+ let bump_multiplier = 1150u128 . pow ( retry_count as u32 ) ; // 15% bump
445
+ let blob_bump_multiplier = 2000u128 . pow ( retry_count as u32 ) ; // 100% bump (double each time) for blob gas
446
+ let bump_divisor = 1000u128 . pow ( retry_count as u32 ) ;
447
+
448
+ let max_fee_per_gas = base_fee_per_gas * bump_multiplier / bump_divisor;
449
+ let max_priority_fee_per_gas = base_priority_fee_per_gas * bump_multiplier / bump_divisor;
450
+ let max_fee_per_blob_gas = base_fee_per_blob_gas * blob_bump_multiplier / bump_divisor;
451
+
452
+ debug ! (
453
+ retry_count,
454
+ max_fee_per_gas,
455
+ max_priority_fee_per_gas,
456
+ max_fee_per_blob_gas,
457
+ "calculated bumped gas parameters"
458
+ ) ;
459
+
460
+ ( max_fee_per_gas, max_priority_fee_per_gas, max_fee_per_blob_gas)
461
+ }
0 commit comments