Skip to content

feat(l1-sender): In-flight tx detection & minor enhancements#1172

Open
Artemka374 wants to merge 14 commits intomainfrom
afo/detect-in-flight-txs
Open

feat(l1-sender): In-flight tx detection & minor enhancements#1172
Artemka374 wants to merge 14 commits intomainfrom
afo/detect-in-flight-txs

Conversation

@Artemka374
Copy link
Copy Markdown
Contributor

@Artemka374 Artemka374 commented Apr 14, 2026

Summary

  • In-flight transaction recovery on startup. Previously, if the L1 sender was restarted while transactions were still pending in the mempool, it would crash when they eventually got mined. The sender now detects and resumes tracking any such transactions, falling back to the normal send
    path if recovery isn't possible (e.g. unsupported RPC method, dropped mempool transaction).

  • Gas limit tightened. The hardcoded gas limit was reduced from 15M (a ZKsync Era default) to 2M, which is a more realistic ceiling for the actual transactions sent by the L1 sender.

IMPORTANT NOTE

The recovery only works with Reth nodes v1.0.6, as other providers doesn't support eth_getTransactionBySenderAndNonce which is able to check the mempool

Follow-ups

  • Revisit fee params estimation
  • Increase required confirmations to 3. Initialliy planned to be implemented in this PR, but seems there are some issues with tests
  • Probably allow force-replacing the transactions

@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 14, 2026

Test results

291 tests   291 ✅  22m 14s ⏱️
 23 suites    0 💤
  1 files      0 ❌

Results for commit 886491c.

♻️ This comment has been updated with latest results.

Comment thread lib/l1_sender/src/lib.rs
// Probe whether the provider supports `eth_getTransactionBySenderAndNonce` before
// iterating over all pending nonces.
if let Err(TransportError::ErrorResp(ref e)) = provider
.raw_request::<_, Option<TxResponse>>(
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is existing get_transaction_by_sender_nonce that you can use instead

Comment thread lib/l1_sender/src/lib.rs
// For each pending nonce, fetch the in-flight tx then peek at the next queued command.
// If the command's calldata matches what is on-chain, consume and pair it. On the first
// mismatch, stop — the unmatched command stays in `inbound` and will be re-sent by the
// normal send path (replacing the in-flight tx at that nonce).
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, there seems to be a race condition between batcher that relies on last committed batch as discovered during startup and this component that might be doing recovery at a slightly different point in time.

We need a way to align them somehow. Either make this logic resistant to receiving older calldata that is no longer in-flight. Or moving this logic somewhere earlier (but even then I am not sure if we'd be able to avoid race condition).

Comment thread lib/l1_sender/src/lib.rs
for nonce in latest_nonce..pending_nonce {
let tx = match provider
.raw_request::<_, Option<TxResponse>>(
"eth_getTransactionBySenderAndNonce".into(),
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same thing about get_transaction_by_sender_nonce here

Comment thread lib/l1_sender/src/lib.rs
// recovered on startup and prepend them. Their nonces are lower than the ones we
// are about to assign, so they must be first in the ordering. On all subsequent
// iterations `recovered` is empty and this produces nothing.
let mut pending_txs: Vec<(TransactionReceiptFuture, Input, Instant)> = recovered
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's reasonable to wait for all in-flight transactions first before entering this loop. Current state of code is becoming very convoluted... We have pending_txs, new_txs, recovered, commands etc. And all of them are mutable and hard to reason about.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants