fix(core): keep on-chain-confirmed orders retryable on transient local faults#415
fix(core): keep on-chain-confirmed orders retryable on transient local faults#415nahimterrazas wants to merge 1 commit into
Conversation
…l faults Once a fill/claim confirms on-chain, handle_confirmed still reads the order and performs CAS-backed status/hash writes. A transient Storage/State/Service fault on any of those (Redis latency, a lost CAS race after retries, a data-repair miss) was mapped by the TransactionConfirmed catch-all to terminal OrderStatus::Failed. Recovery excludes terminal orders, so an order whose funds already moved on-chain was permanently stranded — the failure mode the engine's idempotency invariant is meant to prevent. Extract the catch-all into SolverEngine::handle_confirmation_error, gated by a new confirmation_failure_policy. Every error reaching it occurs after the !receipt.success gate (the on-chain op already succeeded), so these local faults are classified retryable: the order is left in its current non-terminal status for startup recovery to re-drive the stage, instead of being failed. Genuine on-chain reverts still terminate via the unchanged !receipt.success path. Regression tests (RED-verified before the fix): - confirmation_policy_retries_local_processing_errors - confirmed_state_error_leaves_filled_order_non_terminal - confirmed_storage_error_leaves_claimed_order_non_terminal
📝 WalkthroughWalkthroughThe solver engine now classifies errors occurring after on-chain transaction confirmation, preventing local processing faults from incorrectly marking orders as terminally failed. A new ChangesConfirmation Error Handling
Estimated code review effort: 3 (Moderate) | ~20 minutes Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
crates/solver-core/src/engine/mod.rs (1)
1010-1040: 🧹 Nitpick | 🔵 TrivialNo in-process re-drive for RetryLater confirmations — recovery depends on restart.
For the
RetryLaterpath the order is correctly left non-terminal (good, avoids stranding capital), but nothing re-emitsTransactionConfirmedor otherwise re-drives the stage in-process; per the doc comment, re-driving happens via startup recovery. If a solver stays up for a long time after a transientStorage/State/Servicefault, the order can sit in its non-terminal status until the next restart or an unrelated reconcile.Consider surfacing this via a metric/alert on confirmation-processing failures, or scheduling an in-process reconcile (similar to
spawn_delayed_deferred_retry) so recovery isn't restart-gated. This is a design/ops observation, not a blocker.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@crates/solver-core/src/engine/mod.rs` around lines 1010 - 1040, The RetryLater branch in handle_confirmation_error leaves the order non-terminal but does not re-drive confirmation handling in-process, so recovery depends on restart. Update this path to either schedule an in-process reconcile/retry (similar to spawn_delayed_deferred_retry) or emit a clear metric/alert from handle_confirmation_error so long-lived transient failures are observable and can recover without waiting for startup recovery.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@crates/solver-core/src/engine/mod.rs`:
- Around line 1010-1040: The RetryLater branch in handle_confirmation_error
leaves the order non-terminal but does not re-drive confirmation handling
in-process, so recovery depends on restart. Update this path to either schedule
an in-process reconcile/retry (similar to spawn_delayed_deferred_retry) or emit
a clear metric/alert from handle_confirmation_error so long-lived transient
failures are observable and can recover without waiting for startup recovery.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: ff88bd99-001b-4068-a9ef-ab02ab30b181
📒 Files selected for processing (1)
crates/solver-core/src/engine/mod.rs
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
Summary
A newly found strand in the solver-core order-status-persistence path: an order whose fill/claim already succeeded on-chain could be moved to terminal
Failedby a transient local storage fault, then excluded from recovery — leaking the on-chain outcome and stranding solver capital.handle_confirmedruns the!receipt.successgate first, so everything after it executes only when the on-chain transaction succeeded. From there it still:transaction.rs:227→TransactionError::Storage), andtransaction.rs:372,419,471,522→TransactionError::State).The
TransactionConfirmedcatch-all (engine/mod.rs) mapped every non-SettlementCallbackerror toOrderStatus::Failed(tx_type, …).Failedis terminal (solver-types/order.rs), and recovery only loads non-terminal orders (recovery/mod.rs) and defensively skipsFailed— so a momentary Redis latency spike, a lost CAS race afterORDER_CAS_MAX_RETRIES, or a data-repair miss on an on-chain-confirmed order stranded it permanently.FillandClaimconfirmations have no settlement callback at all, so their only failure path was this catch-all.Fix
SolverEngine::handle_confirmation_error.confirmation_failure_policy: since every error reaching it is a local fault after on-chain success, classifyStorage/State/Serviceas retryable — leave the order in its current non-terminal status so startup recovery re-drives the stage from the attempt ledger + on-chain state, instead of writing terminalFailed.settlement_failure_policy/handle_settlement_stage_errordesign.!receipt.successgate andhandle_failed.No behaviour change for success paths; no new error types.
Tests (RED → GREEN)
Three regression tests, each confirmed RED (order landed in
Failed(Fill)/Failed(Claim); policy returnedFailOrder) before the fix and GREEN after:confirmation_policy_retries_local_processing_errorsconfirmed_state_error_leaves_filled_order_non_terminalconfirmed_storage_error_leaves_claimed_order_non_terminalVerification
cargo test -p solver-core→ full suite passes (3 new)cargo fmt --all -- --check→ cleancargo clippy -p solver-core --all-features --all-targets -- -D warnings --allow deprecated→ cleanSummary by CodeRabbit