fix: uniswapx tx liveness - #115
Conversation
1066ba7 to
0be611c
Compare
Serialize signed lifecycles, preserve replacement and cancellation fee headroom, track ambiguous broadcasts by exact hash, and drain accepted work safely. Use tipGwei as a minimum over the RPC suggestion while keeping request and global fee caps fail-closed.
4a543e8 to
ec4206f
Compare
|
Reviewed the txmanager rewrite end to end. Build is green, the 1. A replacement/cancellation can be broadcast underpriced and permanently stall the nonce laneIn That rejection then compounds. Fix: require 2. Graceful shutdown can hang unbounded
Fix: bound the drain. Select on 3. A replacement that races the original mining spuriously pauses the whole lane
Fix: skip 4. ValidateTxManager does not check tipGwei <= maxFeeGwei
5. (low) All-zero p75 fee history gives a 0-wei tip and 1-wei replacement stepsWith Nits
|
Pin each confirmation snapshot to one read endpoint and reject priority-fee floors that cannot fit beneath the reserved replacement bumps. Remove the unrelated RFQ and LI.FI CancelAt expansion and document crash-recovery limits.
Prove receipt ancestry across fallback RPC reads and retain per-request failover. Clamp advisory priority fees to cap headroom, account for UniswapX chain-time read latency, and document restart limitations.
|
Re-reviewed at cd18784. The important fixes land correctly: 1. Shutdown still hangs unbounded when the active lifecycle cannot resolveUnchanged from my last review.
With an active nonce conflict, line 593 makes every cancellation attempt a no-op, no tracked hash can ever confirm, and line 232 blocks forever. Same for a sustained write-RPC outage. SIGTERM then requires SIGKILL, which is the mid-flight kill this design is trying to avoid. The README now documents the fail-closed restart, but the drain itself still needs a bound. 2. A replacement racing the original's inclusion still pauses the lane
if isNonceConsumedError(err) || (!existingLifecycle && isPendingNonceCollision(err)) {
m.markNonceConflict(signed.Nonce(), signed.Hash())A periodic replacement signed just as the original mines gets "nonce too low" and marks a conflict even though we own the nonce. Readiness and admissions drop for the full confirmation window on a healthy fill. Skipping the mark when 3. New in this update: removing
|
- bound shutdown drains and safely reconcile replacement inclusion races - restore LI.FI and RFQ cancellation deadlines with shared chain-time translation
|
Re-reviewed at 39e973e. All three items from my last comment are fixed properly: the bounded shutdown drain ( One finding I had not put on the record yet, and it is the main thing I would still fix in this PR. It follows directly from the single-lifecycle redesign. UniswapX quoting reopens at fill admission, not at lifecycle terminal
Before this PR that was fine: nonces pipelined, so a second fill was signed at nonce+1 and broadcast within seconds. Now the timeline is:
So under single-lifecycle, the solver overcommits: it keeps winning exclusive obligations at a rate it can no longer physically serve. The existing gates show the invariant "do not quote when you cannot fill" was intended; it is just anchored to the wrong endpoint of a fill's life. Fix direction: an atomic in-flight counter incremented at admission (next to Two related smaller items, same root cause
All three are one root cause: the PR changed what "busy" means but only the txmanager knows it. The quote gate still thinks busy means planning, the breaker still thinks every error was on-chain, and the fill loop still thinks the lane is always worth preparing for. The first one is the one that costs reputation. |
Expose txmanager lane occupancy through terminal lifecycle completion and distinguish manager-level admission failures from submitted fill failures. Defer expensive fill planning during nonce conflicts and keep operator documentation aligned with the single-lifecycle model.
oxsteins
left a comment
There was a problem hiding this comment.
Reviewed through b610dfc. All three items from my last comment are fixed and test-covered: the manager-level Idle() gate blocks quoting and readiness from slot demand through the terminal result (a better shape than the solver-side counter I suggested, since it also covers waiters and fills from other solvers sharing the lane), manager-level admission failures are now typed NotAdmitted and routed to uniswapx_fills_total{outcome="not-admitted"} without touching the fade breaker (while fee/gas/signing failures deliberately still count, which is right), and the fill loop defers claimed orders before any chain reads or discount resolution while the lane is paused. I traced the admissionDemand accounting across every path and it balances. Full race suite passes.
One thing left before this can land: with #117 merged into stage, this branch now reports a real merge conflict. The reconciliation touches exactly the seams where the two PRs made contradictory choices, so it deserves care rather than a mechanical merge:
cmd/vault-solver/run.go: #117's ShutdownPreparer/drain-monitor shutdown vs this PR's watchReadiness/ReportFatal plus the bounded txmanager drain. The two models overlap; pick one composition rather than stacking both timers.internal/solver/solver.go: both PRs added interfaces; textual conflict, semantically composable.- The LI.FI completion path is the dangerous one: this branch releases the fill reservation immediately in
completeFill(internal/solvers/lifi/submission.go), while merged #117 defers the release until after its capacity-retry batch (execution.go, viaSnapshotExcluding) precisely so capacity is never transiently freed. These merge cleanly textually and contradict semantically; keep #117's deferred-release sequence and drop the immediate release, or the retry planning can observe transiently freed capacity and over-quote.
Approving on the strength of the branch as it stands. Happy to re-review the stage reconciliation commit when it is up, since that is where the remaining risk lives.
Make initial signing cancellation-aware, fall through lagging null receipt/header reads, propagate nonce-lane pauses to external commitment paths, and require positive base-fee headroom.
Keep normal admissions waiting without signing across nonce conflicts, while non-blocking sends still fail fast. Publish occupied/conflicted lane state to readiness and every external commitment producer, with focused regressions and synchronized operator/design docs.
Join the RFQ execution loop after stopping intake so admitted txmanager results are recorded before solver exit. Report listener failures before draining to activate the bounded process shutdown, and cover both cancellation and fatal-server paths.
No description provided.