Skip to content

feat(evm): handle nonce gaps before resubmission#726

Merged
zeljkoX merged 13 commits intomainfrom
evm-nonce-handling
Apr 8, 2026
Merged

feat(evm): handle nonce gaps before resubmission#726
zeljkoX merged 13 commits intomainfrom
evm-nonce-handling

Conversation

@zeljkoX
Copy link
Copy Markdown
Collaborator

@zeljkoX zeljkoX commented Mar 25, 2026

Summary

  • Add detect_nonce_gap_ahead in the status checker — when a Submitted tx is stale and due for resubmit,
    checks if it's blocked by nonce gaps below it. If gaps found, schedules a nonce health job instead of
    futile resubmission.
  • Schedule nonce health job from reconcile_tx_nonce_state when external nonce consumption is detected.
  • Refactor detect_nonce_gaps to scan the Redis nonce index
    instead of relying on the transaction counter, which can be stale after restarts.
  • Add nonce hint to health job metadata so resolve_nonce_gaps can raise the counter when it was reset
    below existing tx nonces.

Testing Process

Checklist

  • Add a reference to related issues in the PR description.
  • Add unit tests if applicable.

Note

If you are using Relayer in your stack, consider adding your team or organization to our list of Relayer Users in the Wild!

Summary by CodeRabbit

  • New Features

    • Added automatic nonce gap detection and resolution for EVM transactions
    • Implemented nonce health check actions with optional nonce-hint guidance
    • Enhanced transaction error classification for nonce-related RPC failures
  • Bug Fixes

    • Improved handling of nonce-too-high and nonce-too-low submission errors
    • Better transaction state reconciliation during status checks
    • Fixed transaction retry logic to prevent unnecessary resubmissions
  • Refactor

    • Restructured nonce management into dedicated module
    • Enhanced transaction metadata tracking for retry counts

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 25, 2026

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: df275d13-8d28-445f-9fb0-680e914a4f4e

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Walkthrough

This PR introduces comprehensive nonce management for EVM relayers by extracting nonce synchronization into a dedicated module, implementing nonce-gap detection and resolution with health-check actions, adding RPC error classification for transaction submission flows, and extending repositories with nonce occupancy queries. The changes unify error pattern matching, escalate nonce-too-high failures to health-check workflows, and enable fast-path nonce reconciliation during status checks.

Changes

Cohort / File(s) Summary
Constants & Error Classification
src/constants/evm_transaction.rs, src/services/provider/mod.rs
Added RPC error pattern constants (ALREADY_SUBMITTED_PATTERNS, NONCE_TOO_HIGH_PATTERNS), health-check metadata keys, and helper functions (matches_known_transaction, is_non_retriable_transaction_rpc_message) for error classification. Updated provider error retry logic to treat transaction-level rejections as non-retriable.
EVM Relayer Refactoring
src/domain/relayer/evm/evm_relayer.rs, src/domain/relayer/evm/mod.rs, src/domain/relayer/evm/nonce.rs
Extracted sync_nonce from evm_relayer.rs into new nonce.rs module; changed field visibility to pub(super). New module implements gap detection (detect_nonce_gaps), resolution (resolve_nonce_gaps), health-action handling (handle_health_action), and NOOP gap-filling with optional nonce hints.
Transaction Submission Error Handling
src/domain/transaction/evm/evm_transaction.rs
Introduced SubmissionErrorKind enum and classify_submission_error for pattern-based error categorization. Added retry counter escalation (handle_nonce_too_high), nonce reconciliation metadata injection (schedule_nonce_recovery_status_check), and health-job scheduling. Modified submit/resubmit flows to handle NonceTooLow, NonceTooHigh, and known-transaction errors without advancing status/hash.
Status Checking & Nonce Reconciliation
src/domain/transaction/evm/status.rs
Added detect_nonce_gap_ahead for early gap detection and reconcile_tx_nonce_state for fast-path nonce reconciliation via receipt checking and historical hash recovery. Updated circuit-breaker behavior for Sent status to issue NOOP instead of marking failed. Integrated metadata-driven reconciliation trigger into main status flow.
Relayer Trait & Health Actions
src/domain/relayer/mod.rs
Added handle_health_action trait method to Relayer with default Ok(false) implementation; routed through NetworkRelayer to underlying EVM relayer or defaults for non-EVM networks.
Transaction Common Utilities
src/domain/transaction/common.rs
Added is_active_nonce_status utility to classify Pending, Sent, Submitted, Mined as active and terminal statuses (Confirmed, Failed, Canceled, Expired) as inactive.
Repository Nonce Occupancy
src/repositories/transaction/mod.rs, src/repositories/transaction/transaction_in_memory.rs, src/repositories/transaction/transaction_redis.rs
Extended TransactionRepository trait with get_nonce_occupancy(from_nonce, to_nonce) returning occupancy map. Implemented for in-memory and Redis backends using direct lookup and batched MGET respectively.
Job Infrastructure
src/jobs/job.rs, src/jobs/status_check_context.rs, src/jobs/handlers/relayer_health_check_handler.rs, src/jobs/handlers/transaction_status_handler.rs
Added optional metadata field to RelayerHealthCheck with builders (with_metadata, nonce_health, nonce_health_with_hint). Extended StatusCheckContext with job_metadata field. Updated health-check handler to dispatch targeted nonce-health actions and status-check handler to propagate metadata.
Transaction Metadata & Models
src/models/transaction/repository.rs
Added nonce_too_high_retries: u32 counter to TransactionMetadata with with_nonce_retries_reset() helper method for counter reset logic.
Test Updates
src/domain/transaction/stellar/submit.rs, src/services/provider/retry.rs
Updated Stellar test fixtures with new nonce_too_high_retries: 0 field; added provider retry test validating non-retriable RPC error handling.

Sequence Diagrams

sequenceDiagram
    actor Client
    participant EVM Relayer
    participant Transaction Handler
    participant Repository
    participant Provider

    Client->>EVM Relayer: detect_nonce_gaps(on_chain_nonce)
    EVM Relayer->>Provider: get_transaction_count (on-chain nonce)
    Provider-->>EVM Relayer: nonce
    EVM Relayer->>Repository: get_nonce_occupancy(on_chain..tx_nonce)
    Repository-->>EVM Relayer: Vec<(nonce, Option<Status>)>
    EVM Relayer-->>Client: gaps_detected: Vec<u64>
    
    Note over Client,EVM Relayer: Gap resolution triggered by health action
    Client->>EVM Relayer: resolve_nonce_gaps(nonce_hint)
    EVM Relayer->>EVM Relayer: optionally raise counter via nonce_hint
    EVM Relayer->>EVM Relayer: pause to mitigate race conditions
    EVM Relayer->>EVM Relayer: sync_nonce (re-sync with chain)
    EVM Relayer->>EVM Relayer: detect_nonce_gaps (re-check)
    loop For each remaining gap
        EVM Relayer->>EVM Relayer: create_gap_filling_noop(nonce)
        EVM Relayer->>Repository: persist NOOP as Pending
        EVM Relayer->>Client: enqueue transaction + status-check job
    end
    EVM Relayer-->>Client: gaps_filled: usize
Loading
sequenceDiagram
    actor Job Handler
    participant Health Check Handler
    participant EVM Relayer
    participant Nonce Module

    Job Handler->>Health Check Handler: handle_relayer_health_check(metadata)
    alt metadata present and action = nonce_health
        Health Check Handler->>EVM Relayer: handle_health_action(metadata)
        EVM Relayer->>Nonce Module: resolve_nonce_gaps(nonce_hint from metadata)
        Nonce Module->>Nonce Module: detect, fill, sync workflow
        Nonce Module-->>EVM Relayer: Result<usize, Error>
        EVM Relayer-->>Health Check Handler: Ok(true) - action handled
        Health Check Handler-->>Job Handler: Ok(())
    else no metadata or action unhandled
        Health Check Handler->>Health Check Handler: run normal health check
        Health Check Handler-->>Job Handler: Ok(())
    end
Loading

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly Related PRs

Suggested Labels

cla: allowlist

Suggested Reviewers

  • dylankilkenny
  • tirumerla

Poem

🐰 Gaps in nonces? Time to fill!
With health checks and NOOP skill,
Metadata guides each relay's dance,
As occupancy queries enhance,
The chain stays whole—no slip, no spill!
🌟

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Description check ✅ Passed PR description covers the main objectives (nonce gap detection, reconciliation, refactoring, nonce hints) and testing checklist is partially completed.
Title check ✅ Passed The title accurately summarizes the main change: proactive handling of nonce gaps before resubmission in EVM transactions, which is the central focus of this PR.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch evm-nonce-handling

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR improves EVM transaction reliability by proactively detecting nonce gaps (and externally-consumed nonces) and routing recovery through targeted relayer “nonce health” jobs instead of repeatedly resubmitting transactions that cannot be mined.

Changes:

  • Add nonce-gap-ahead detection in EVM status checking to schedule nonce health jobs when a stale Submitted tx is blocked by missing/terminal nonces below it.
  • Introduce repository-level get_nonce_occupancy for efficient nonce-slot scans (batched Redis MGET; in-memory implementation for tests).
  • Enhance nonce-related error classification (provider retry behavior + EVM submission/resubmission handling), plus add health-check metadata and a dedicated EVM nonce health implementation.

Reviewed changes

Copilot reviewed 19 out of 19 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/services/provider/retry.rs Adds a regression test ensuring non-retriable tx-level RPC errors don’t trigger retries/failover.
src/services/provider/mod.rs Classifies certain RPC error-code messages as non-retriable “tx-level” rejections; adds unit tests.
src/repositories/transaction/mod.rs Extends TransactionRepository with get_nonce_occupancy and wires it through storage/mock.
src/repositories/transaction/transaction_redis.rs Implements get_nonce_occupancy using batched Redis MGET; adds integration tests.
src/repositories/transaction/transaction_in_memory.rs Implements get_nonce_occupancy for the in-memory repository; adds unit tests.
src/models/transaction/repository.rs Adds nonce_too_high_retries tracking + helper reset method; adds backward-compat tests.
src/jobs/status_check_context.rs Adds optional job metadata to StatusCheckContext to support one-shot recovery hints.
src/jobs/job.rs Adds metadata support to RelayerHealthCheck and constructors for nonce-health jobs (with optional nonce hint).
src/jobs/handlers/transaction_status_handler.rs Plumbs job metadata into StatusCheckContext.
src/jobs/handlers/relayer_health_check_handler.rs Executes targeted health actions (like nonce health) based on job metadata.
src/domain/transaction/stellar/submit.rs Updates tests/metadata structs to include the new nonce_too_high_retries field.
src/domain/transaction/common.rs Adds is_active_nonce_status helper used for nonce-gap logic and tests it.
src/domain/transaction/evm/status.rs Adds nonce-gap-ahead detection + nonce reconciliation path triggered by job metadata; updates circuit-breaker behavior for Sent.
src/domain/transaction/evm/evm_transaction.rs Adds nonce error classification, nonce-too-high retry escalation, and status-check metadata scheduling.
src/domain/relayer/mod.rs Adds a default handle_health_action method to the Relayer trait and forwards in NetworkRelayer.
src/domain/relayer/evm/mod.rs Registers the new EVM nonce module.
src/domain/relayer/evm/evm_relayer.rs Exposes internal fields needed by the new nonce module and removes the old sync_nonce impl from this file.
src/domain/relayer/evm/nonce.rs New module implementing nonce sync/gap detection/gap filling and targeted nonce-health action handling.
src/constants/evm_transaction.rs Adds shared nonce/tx error pattern constants and health-check metadata keys.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/repositories/transaction/transaction_redis.rs
Comment thread src/domain/relayer/evm/nonce.rs Outdated
Comment thread src/constants/evm_transaction.rs
Comment thread src/domain/transaction/evm/status.rs Outdated
@codecov
Copy link
Copy Markdown

codecov Bot commented Mar 25, 2026

Codecov Report

❌ Patch coverage is 93.84690% with 127 lines in your changes missing coverage. Please review.
✅ Project coverage is 90.31%. Comparing base (cf553ef) to head (3b346a4).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
src/domain/transaction/evm/status.rs 89.78% 56 Missing ⚠️
src/domain/relayer/evm/nonce.rs 93.79% 39 Missing ⚠️
src/domain/transaction/evm/evm_transaction.rs 97.55% 15 Missing ⚠️
src/jobs/handlers/relayer_health_check_handler.rs 20.00% 8 Missing ⚠️
src/domain/relayer/mod.rs 0.00% 4 Missing ⚠️
src/constants/evm_transaction.rs 94.59% 2 Missing ⚠️
src/repositories/transaction/mod.rs 0.00% 2 Missing ⚠️
src/jobs/handlers/transaction_status_handler.rs 66.66% 1 Missing ⚠️
Additional details and impacted files
Flag Coverage Δ
ai 0.00% <0.00%> (ø)
dev 90.31% <93.84%> (+0.08%) ⬆️
properties 0.01% <0.00%> (-0.01%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

@@            Coverage Diff             @@
##             main     #726      +/-   ##
==========================================
+ Coverage   90.21%   90.31%   +0.10%     
==========================================
  Files         290      291       +1     
  Lines      122099   124258    +2159     
==========================================
+ Hits       110147   112219    +2072     
- Misses      11952    12039      +87     
Files with missing lines Coverage Δ
src/domain/relayer/evm/evm_relayer.rs 99.27% <ø> (-0.01%) ⬇️
src/domain/transaction/common.rs 97.89% <100.00%> (+0.36%) ⬆️
src/domain/transaction/stellar/submit.rs 95.17% <100.00%> (+0.01%) ⬆️
src/jobs/handlers/transaction_cleanup_handler.rs 86.40% <100.00%> (ø)
src/jobs/job.rs 96.09% <100.00%> (+1.12%) ⬆️
src/jobs/status_check_context.rs 100.00% <100.00%> (ø)
src/models/transaction/repository.rs 95.46% <100.00%> (+0.12%) ⬆️
.../repositories/transaction/transaction_in_memory.rs 98.23% <100.00%> (+0.04%) ⬆️
src/services/provider/mod.rs 93.46% <100.00%> (+1.67%) ⬆️
src/jobs/handlers/transaction_status_handler.rs 61.46% <66.66%> (+0.05%) ⬆️
... and 7 more

... and 2 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (5)
src/repositories/transaction/transaction_in_memory.rs (1)

323-334: Take one store snapshot for the whole occupancy scan.

This loops through find_by_nonce, so each nonce reacquires the mutex and rescans the full map. A single lock keeps the returned window consistent and avoids O(range × store_size) work in the reference implementation.

♻️ Possible simplification
     async fn get_nonce_occupancy(
         &self,
         relayer_id: &str,
         from_nonce: u64,
         to_nonce: u64,
     ) -> Result<Vec<(u64, Option<TransactionStatus>)>, RepositoryError> {
-        let mut results = Vec::new();
-        for nonce in from_nonce..to_nonce {
-            let tx = self.find_by_nonce(relayer_id, nonce).await?;
-            results.push((nonce, tx.map(|t| t.status)));
-        }
+        let store = Self::acquire_lock(&self.store).await?;
+        let mut results = Vec::with_capacity(to_nonce.saturating_sub(from_nonce) as usize);
+        for nonce in from_nonce..to_nonce {
+            let status = store
+                .values()
+                .find(|tx| {
+                    tx.relayer_id == relayer_id
+                        && matches!(
+                            &tx.network_data,
+                            NetworkTransactionData::Evm(data) if data.nonce == Some(nonce)
+                        )
+                })
+                .map(|tx| tx.status.clone());
+            results.push((nonce, status));
+        }
         Ok(results)
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/repositories/transaction/transaction_in_memory.rs` around lines 323 -
334, The get_nonce_occupancy implementation currently calls find_by_nonce inside
the loop, re-locking and rescanning the store for each nonce; instead acquire
the in-memory store lock once, take a snapshot (clone or iterate the map while
holding the lock) and then release the lock and build the Vec<(u64,
Option<TransactionStatus>)> from that snapshot for nonces in
from_nonce..to_nonce. Replace per-iteration find_by_nonce calls with lookups
into the snapshot, keeping the function signature and returned TransactionStatus
mapping unchanged.
src/jobs/job.rs (1)

267-270: with_metadata is destructive on routed health-check jobs.

Because this replaces the whole map, chaining it after nonce_health() / nonce_health_with_hint() would discard the routing keys those builders add, and schedule_relayer_nonce_health_job in src/domain/transaction/evm/evm_transaction.rs:277-301 would stop routing the job as a nonce-health action. Consider merging into the existing map here, or rename this to replace_metadata and add a short doc comment so the behavior is explicit.
As per coding guidelines "Include relevant doc comments (///) on public functions, structs, and modules. Use comments for 'why', not 'what'. Avoid redundant doc comments".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/jobs/job.rs` around lines 267 - 270, The with_metadata method currently
overwrites the entire metadata map which discards routing keys set by
nonce_health / nonce_health_with_hint and breaks
schedule_relayer_nonce_health_job; change with_metadata to merge the incoming
HashMap into the existing self.metadata (preserving existing keys and
adding/updating entries) or alternatively rename it to replace_metadata and add
a /// doc comment on the public method explaining that it replaces metadata (and
mention nonce_health/nonce_health_with_hint behavior), and update callers if you
rename the method; reference with_metadata, nonce_health,
nonce_health_with_hint, and schedule_relayer_nonce_health_job to locate relevant
code.
src/repositories/transaction/mod.rs (1)

91-101: Give the occupancy result a named type and explicit invariants.

Vec<(u64, Option<TransactionStatus>)> is repeated across the trait, mock, and storage adapter, but the nonce-gap scanner depends on stronger semantics than the signature shows. A small alias or entry type would make the API self-describing and give you one place to document “ascending order, one entry per nonce, and empty when from_nonce >= to_nonce,” which will help keep the two backends aligned.
As per coding guidelines "Use type aliases for complex types to improve readability".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/repositories/transaction/mod.rs` around lines 91 - 101, Replace the
opaque return type Vec<(u64, Option<TransactionStatus>)> on get_nonce_occupancy
with a named type (e.g., NonceOccupancyEntry or type alias NonceOccupancy =
Vec<NonceOccupancyEntry>) and update the trait signature plus the mock and
storage adapter to use that type; document the invariants on that named type:
entries are in ascending nonce order, contain exactly one entry per nonce in
[from_nonce, to_nonce) and the vector is empty when from_nonce >= to_nonce, so
callers (like the nonce-gap scanner) and implementors rely on the same contract.
src/domain/relayer/evm/nonce.rs (1)

62-68: Consider logging counter service errors before falling back.

The .ok().flatten().unwrap_or(0) pattern silently swallows any error from get(). While defaulting to 0 is safe (the sync will use on_chain_nonce), logging the error would help diagnose counter service issues.

🔧 Suggested change
         let transaction_counter_nonce = self
             .transaction_counter_service
             .get()
             .await
-            .ok()
-            .flatten()
-            .unwrap_or(0);
+            .map_err(|e| {
+                debug!(error = %e, "failed to get counter, defaulting to 0");
+                e
+            })
+            .ok()
+            .flatten()
+            .unwrap_or(0);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/domain/relayer/evm/nonce.rs` around lines 62 - 68, The call to
transaction_counter_service.get() currently swallows errors via
.ok().flatten().unwrap_or(0); change this to capture the Result from
transaction_counter_service.get().await (or inspect the Option<Result<...>>) and
log any Err or error-containing Option before falling back to 0, then assign the
fallback to transaction_counter_nonce; reference the
transaction_counter_service.get() call and the transaction_counter_nonce
assignment so you add a processLogger/debug!("...") or similar logging on error
and preserve the existing unwrap_or(0) behavior.
src/domain/transaction/evm/evm_transaction.rs (1)

237-241: Minor: Use imported NetworkType instead of full path.

NetworkType is already imported at line 32, so the full path here is unnecessary.

Suggested fix
         let mut job = TransactionStatusCheck::new(
             tx.id.clone(),
             tx.relayer_id.clone(),
-            crate::models::NetworkType::Evm,
+            NetworkType::Evm,
         );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/domain/transaction/evm/evm_transaction.rs` around lines 237 - 241,
Replace the fully-qualified enum path when constructing the
TransactionStatusCheck: in the TransactionStatusCheck::new call that passes
tx.id.clone() and tx.relayer_id.clone(), change crate::models::NetworkType::Evm
to the imported NetworkType::Evm to use the existing import and avoid the
redundant full path.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/constants/evm_transaction.rs`:
- Around line 166-180: Test case includes "future nonce" which isn't covered by
the configured NONCE_TOO_HIGH_PATTERNS; update the test fixtures in
test_nonce_too_high_patterns_match_expected_strings to use a supported substring
(e.g. replace "future nonce" with "nonce too far in the future") or
alternatively add the broader pattern "future nonce" to the
NONCE_TOO_HIGH_PATTERNS constant so the test matches actual provider responses;
locate NONCE_TOO_HIGH_PATTERNS and the test function name to apply the chosen
change.

In `@src/repositories/transaction/transaction_redis.rs`:
- Around line 1614-1621: The get_nonce_occupancy path currently obtains a
connection from self.get_connection(self.connections.reader(),
"get_nonce_occupancy") which risks replica lag and fabricated nonce gaps; change
it to acquire a primary/writer connection (e.g.
self.get_connection(self.connections.writer() or self.connections.primary(),
"get_nonce_occupancy")) so the MGET against nonce_keys reads the latest
committed data; keep the existing error mapping (map_redis_error call) unchanged
except ensure the same context string ("get_nonce_occupancy:mget_nonces") is
used.

In `@src/services/provider/mod.rs`:
- Around line 1311-1319: The test is asserting against a string ("future nonce")
that isn't in the shared NONCE_TOO_HIGH_PATTERNS used by
is_non_retriable_transaction_rpc_message; update the test to use the canonical
pattern present in NONCE_TOO_HIGH_PATTERNS (e.g., "nonce too far in the future")
or remove the incorrect assertion so the test matches production behavior
checked by is_non_retriable_transaction_rpc_message.

---

Nitpick comments:
In `@src/domain/relayer/evm/nonce.rs`:
- Around line 62-68: The call to transaction_counter_service.get() currently
swallows errors via .ok().flatten().unwrap_or(0); change this to capture the
Result from transaction_counter_service.get().await (or inspect the
Option<Result<...>>) and log any Err or error-containing Option before falling
back to 0, then assign the fallback to transaction_counter_nonce; reference the
transaction_counter_service.get() call and the transaction_counter_nonce
assignment so you add a processLogger/debug!("...") or similar logging on error
and preserve the existing unwrap_or(0) behavior.

In `@src/domain/transaction/evm/evm_transaction.rs`:
- Around line 237-241: Replace the fully-qualified enum path when constructing
the TransactionStatusCheck: in the TransactionStatusCheck::new call that passes
tx.id.clone() and tx.relayer_id.clone(), change crate::models::NetworkType::Evm
to the imported NetworkType::Evm to use the existing import and avoid the
redundant full path.

In `@src/jobs/job.rs`:
- Around line 267-270: The with_metadata method currently overwrites the entire
metadata map which discards routing keys set by nonce_health /
nonce_health_with_hint and breaks schedule_relayer_nonce_health_job; change
with_metadata to merge the incoming HashMap into the existing self.metadata
(preserving existing keys and adding/updating entries) or alternatively rename
it to replace_metadata and add a /// doc comment on the public method explaining
that it replaces metadata (and mention nonce_health/nonce_health_with_hint
behavior), and update callers if you rename the method; reference with_metadata,
nonce_health, nonce_health_with_hint, and schedule_relayer_nonce_health_job to
locate relevant code.

In `@src/repositories/transaction/mod.rs`:
- Around line 91-101: Replace the opaque return type Vec<(u64,
Option<TransactionStatus>)> on get_nonce_occupancy with a named type (e.g.,
NonceOccupancyEntry or type alias NonceOccupancy = Vec<NonceOccupancyEntry>) and
update the trait signature plus the mock and storage adapter to use that type;
document the invariants on that named type: entries are in ascending nonce
order, contain exactly one entry per nonce in [from_nonce, to_nonce) and the
vector is empty when from_nonce >= to_nonce, so callers (like the nonce-gap
scanner) and implementors rely on the same contract.

In `@src/repositories/transaction/transaction_in_memory.rs`:
- Around line 323-334: The get_nonce_occupancy implementation currently calls
find_by_nonce inside the loop, re-locking and rescanning the store for each
nonce; instead acquire the in-memory store lock once, take a snapshot (clone or
iterate the map while holding the lock) and then release the lock and build the
Vec<(u64, Option<TransactionStatus>)> from that snapshot for nonces in
from_nonce..to_nonce. Replace per-iteration find_by_nonce calls with lookups
into the snapshot, keeping the function signature and returned TransactionStatus
mapping unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b8f60d94-69c3-49fa-8f2e-228c7067de17

📥 Commits

Reviewing files that changed from the base of the PR and between d5c5f13 and 78f3e91.

📒 Files selected for processing (19)
  • src/constants/evm_transaction.rs
  • src/domain/relayer/evm/evm_relayer.rs
  • src/domain/relayer/evm/mod.rs
  • src/domain/relayer/evm/nonce.rs
  • src/domain/relayer/mod.rs
  • src/domain/transaction/common.rs
  • src/domain/transaction/evm/evm_transaction.rs
  • src/domain/transaction/evm/status.rs
  • src/domain/transaction/stellar/submit.rs
  • src/jobs/handlers/relayer_health_check_handler.rs
  • src/jobs/handlers/transaction_status_handler.rs
  • src/jobs/job.rs
  • src/jobs/status_check_context.rs
  • src/models/transaction/repository.rs
  • src/repositories/transaction/mod.rs
  • src/repositories/transaction/transaction_in_memory.rs
  • src/repositories/transaction/transaction_redis.rs
  • src/services/provider/mod.rs
  • src/services/provider/retry.rs

Comment thread src/constants/evm_transaction.rs
Comment thread src/repositories/transaction/transaction_redis.rs
Comment thread src/services/provider/mod.rs
Comment thread src/domain/relayer/evm/nonce.rs Dismissed
Comment thread src/domain/relayer/evm/nonce.rs Dismissed
Comment thread src/domain/relayer/evm/nonce.rs Dismissed
Comment thread src/repositories/transaction/transaction_redis.rs Dismissed
Comment thread src/repositories/transaction/transaction_redis.rs Dismissed
@zeljkoX zeljkoX changed the title feat: proactive nonce gap detection and resolution for EVM transactions feat: nonce gap detection and resolution for EVM txs Mar 25, 2026
@zeljkoX zeljkoX changed the title feat: nonce gap detection and resolution for EVM txs feat: Nonce gap detection and resolution for EVM txs Mar 26, 2026
@tirumerla tirumerla changed the title feat: Nonce gap detection and resolution for EVM txs feat: nonce gap detection and resolution for EVM txs Apr 8, 2026
@tirumerla tirumerla changed the title feat: nonce gap detection and resolution for EVM txs feat(EVM): Nonce gap detection and resolution for txs Apr 8, 2026
@tirumerla tirumerla changed the title feat(EVM): Nonce gap detection and resolution for txs feat(evm): handle nonce gaps before resubmission Apr 8, 2026
@zeljkoX zeljkoX merged commit 2c785fd into main Apr 8, 2026
26 checks passed
@zeljkoX zeljkoX deleted the evm-nonce-handling branch April 8, 2026 12:15
@github-actions github-actions Bot locked and limited conversation to collaborators Apr 8, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Nonce counter management bugs: RESET_STORAGE_ON_START doesn't clear counters, no periodic re-sync, INCR race condition

4 participants