Skip to content

Commit 404dfe1

Browse files
authored
Fix duplicate submitted -> pending in case of gas bumped transactions (#53)
1 parent 08bd0e1 commit 404dfe1

File tree

1 file changed

+30
-17
lines changed

1 file changed

+30
-17
lines changed

executors/src/eoa/store/submitted.rs

Lines changed: 30 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -339,21 +339,18 @@ impl SafeRedisTransaction for CleanSubmittedTransactions<'_> {
339339
continue;
340340
}
341341

342-
// Process each unique transaction_id once
343-
if processed_ids.insert(tx.transaction_id()) {
342+
// Do business logic only once per unique transaction_id
343+
let is_first_occurrence = processed_ids.insert(tx.transaction_id());
344+
345+
if is_first_occurrence {
344346
match (tx.transaction_id(), confirmed_ids.get(tx.transaction_id())) {
345347
// if the transaction id is noop, we don't do anything but still clean up
346348
(NO_OP_TRANSACTION_ID, _) => {
347-
// Clean up noop transaction from Redis
348-
self.remove_transaction_from_redis_submitted_zset(pipeline, tx);
349349
report.noop_count += 1;
350350
}
351351

352-
// in case of a valid ID, we check if it's in the confirmed transactions
353-
// if it is confirmed, we succeed it and queue success jobs
352+
// SCENARIO 1: Transaction ID was confirmed - do confirmed business logic
354353
(id, Some(confirmed_tx)) => {
355-
// Clean up confirmed transaction from Redis
356-
self.remove_transaction_from_redis_submitted_zset(pipeline, tx);
357354
let data_key_name = self.keys.transaction_data_key_name(id);
358355
pipeline.hset(&data_key_name, "status", "confirmed");
359356
pipeline.hset(&data_key_name, "completed_at", now);
@@ -392,26 +389,20 @@ impl SafeRedisTransaction for CleanSubmittedTransactions<'_> {
392389
&mut tx_context,
393390
self.webhook_queue.clone(),
394391
) {
395-
tracing::error!("Failed to queue webhook for fail: {}", e);
392+
tracing::error!("Failed to queue webhook for confirmed transaction: {}", e);
396393
}
397394
}
398395
}
399396

400397
report.moved_to_success += 1;
401398
}
402399

403-
// if the ID is not in the confirmed transactions, we queue it for pending
400+
// SCENARIO 2: Transaction ID was NOT confirmed - check if we should replace it
404401
_ => {
405402
if let SubmittedTransactionHydrated::Real(tx) = tx {
406403
// Only move to pending (replaced) if it's within the latest transaction count range
407404
// This prevents false replacements due to flashblocks propagation delays
408405
if tx_nonce <= self.transaction_counts.latest {
409-
// Clean up replaced transaction from Redis
410-
self.remove_transaction_from_redis_submitted_zset(
411-
pipeline,
412-
&SubmittedTransactionHydrated::Real(tx.clone()),
413-
);
414-
415406
// zadd_multiple expects (score, member)
416407
replaced_transactions
417408
.push((tx.queued_at, tx.transaction_id.clone()));
@@ -434,7 +425,7 @@ impl SafeRedisTransaction for CleanSubmittedTransactions<'_> {
434425
&mut tx_context,
435426
self.webhook_queue.clone(),
436427
) {
437-
tracing::error!("Failed to queue webhook for fail: {}", e);
428+
tracing::error!("Failed to queue webhook for replaced transaction: {}", e);
438429
}
439430
}
440431

@@ -451,11 +442,33 @@ impl SafeRedisTransaction for CleanSubmittedTransactions<'_> {
451442
"Keeping transaction in submitted state due to potential flashblocks propagation delay"
452443
);
453444
// Don't increment any counter - transaction stays in submitted state
445+
// Note: We still need to check if we should clean up this hash below
454446
}
455447
}
456448
}
457449
}
458450
}
451+
452+
// Redis cleanup logic: Clean up hashes based on transaction-level decisions
453+
match (tx.transaction_id(), confirmed_ids.get(tx.transaction_id())) {
454+
// SCENARIO 1: Transaction ID was confirmed - remove ALL hashes (even if beyond latest due to flashblocks)
455+
(_, Some(_)) => {
456+
self.remove_transaction_from_redis_submitted_zset(pipeline, tx);
457+
}
458+
// NOOP transactions: always clean up
459+
(NO_OP_TRANSACTION_ID, _) => {
460+
self.remove_transaction_from_redis_submitted_zset(pipeline, tx);
461+
}
462+
// SCENARIO 2: Transaction ID was NOT confirmed - only clean up if within latest confirmed nonce range
463+
_ => {
464+
if let SubmittedTransactionHydrated::Real(_) = tx {
465+
if tx_nonce <= self.transaction_counts.latest {
466+
self.remove_transaction_from_redis_submitted_zset(pipeline, tx);
467+
}
468+
// If beyond latest confirmed nonce, keep in submitted state (don't clean up)
469+
}
470+
}
471+
}
459472
}
460473

461474
if !replaced_transactions.is_empty() {

0 commit comments

Comments
 (0)