feat(bob): concurrent swaps#971
Conversation
|
We should display the final state of the swap until the user acknowledges it. |
|
We should move the "cancel timeline" from the "history" page to the "swaps" page and hide it somewhat (make it expandable). |
|
Issue:
|
|
The interaction with the |
Yes you are right but the whole watcher should become redundant anyway... It should not be required if the state machine is properly working. The watcher should be abolished at some point. |
|
I have 2 swaps where I had to do the XMR redeem manually which are now always automatically resumed - and keep failing. There needs to be a solution to this. Some kind of manually marking swaps as "do not resume" which is persisted across restarts. Apart from that, I think we should change "Suspend" in the GUI to something like "Stop" or "Halt". |
"Stop" or "Halt" seems like this actually stop the timelocks from counting down or does something on a protocol level. I dont see them being clearer than "suspend". |
Suspend has the same issue, except I have a much harder time imagining what "suspend" means. It's technical jargon. |
|
I'll remove the watcher. What do you propose for the manually-finished-swap problem? We could just add a new db table with columns like |
We could also bundle it with #932 |
|
Not sure if removing the watcher is the move yet... its a reasonable safety fallback mechanism. |
|
It also turns out we use the watcher for balance and timelock updates. I'll keep the watcher and make it interact with the swap manager.
Yes. Although there still should be an option to stop a swap from resuming without fully deleting it. It needs to be done as part of this PR though. Otherwise it gets really annoying for people in this situation of which there are a few. |
|
@Einliterflasche I will merge this today. |
|
We definitely need a docker test which spawns multiple swaps. Also get the ui mock system to work again, it's pretty broken now. |
Can you expand? It works perfectly fine for me. |
| // If the swap is already running, we can skip the refund | ||
| // The refund will be handled by the state machine | ||
| if let Some(current_swap_id) = self.swap_lock.get_current_swap_id().await { | ||
| if current_swap_id == swap_id { |
There was a problem hiding this comment.
Watcher blocked during error retries
Medium Severity
The background watcher skips cancel/refund whenever SwapManager still lists a swap as running, but failed swaps now stay registered through indefinite exponential backoff retries. If bob::run keeps erroring before making progress, the watcher never runs its safety refund even when cancel timelocks expire.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 90c4b2f. Configure here.
|
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- reserve the running slot during cancel_and_refund so a concurrent start/resume cannot race the refund transaction - replace the oneshot spawn-gate by holding the running-map lock across spawn + insert - unify MakeInitialSwap/RebuildSwap into a single per-swap factory - make run_exclusive_initiation a SwapManager method, emit a terminal Released when the initiation body errors - misc: drop unwrap_or(0) in unix_now_ms, dead watcher import, unused useHasSwapPhaseSwap hook
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 3 potential issues.
There are 4 total unresolved issues (including 1 from previous review).
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 57fc4ec. Configure here.
| return saneSwapInfos.filter( | ||
| (info) => | ||
| isBobStateNameRunningSwap(info.state_name) && swaps[info.swap_id] == null, | ||
| ); |
There was a problem hiding this comment.
Auto-resume hidden from GUI
High Severity
The Swaps tab still lists idle resumable swaps from DB state alone, while auto_resume is only stored in SQLite and never exposed on GetSwapInfoResponse. Swaps marked “Don’t auto resume on startup” keep appearing with Resume, matching reported stale-swap behavior.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 57fc4ec. Configure here.
| isOfferPhase(existingSwap.curr) | ||
| ) { | ||
| delete swap.swaps[action.payload.swap_id]; | ||
| return; |
There was a problem hiding this comment.
Offer cleanup misses retry
Medium Severity
Terminal Released cleanup drops offer-phase swaps only when existingSwap.curr is an offer event. If curr is already a retry Released, offer-phase prev is ignored and the entry stays on the Offers tab until manually closed.
Reviewed by Cursor Bugbot for commit 57fc4ec. Configure here.
| if (phaseEvent == null) return false; | ||
| return mode === "offers" | ||
| ? isOfferPhase(phaseEvent) | ||
| : !isOfferPhase(phaseEvent); |
There was a problem hiding this comment.
Hidden retry backoff panel
Medium Severity
Swaps in retry backoff are omitted from the widget when curr is Released and prev is null, because phaseEvent becomes null and the filter returns false. The retry banner and state UI never mount for that session entry.
Reviewed by Cursor Bugbot for commit 57fc4ec. Configure here.


No description provided.