Skip to content

fix(bob): Explicitly check for tx_punish#938

Merged
binarybaron merged 4 commits intomasterfrom
fix/bob/explicit-punish-check
Mar 31, 2026
Merged

fix(bob): Explicitly check for tx_punish#938
binarybaron merged 4 commits intomasterfrom
fix/bob/explicit-punish-check

Conversation

@binarybaron
Copy link
Copy Markdown

@binarybaron binarybaron commented Mar 31, 2026

Note

Medium Risk
Touches Bob swap state persistence and cancel/refund transition logic, plus adds a SQLite JSON migration; mistakes could misclassify punished vs refundable swaps or break resuming older swaps.

Overview
Fixes Bob getting stuck in BtcCancelled by explicitly detecting punishment: when ExpiredTimelocks::Punish is reached, Bob now constructs TxPunish and queries the chain for its txid before attempting refunds, transitioning to BtcPunished if found.

To support this across resumes, Bob state structs State3State6 now persist tx_punish_fee and punish_address (and State6 gains construct_tx_punish()), with a new SQLite migration that backfills these fields into existing stored JSON states. Changelog notes the GUI-facing fix.

Written by Cursor Bugbot for commit 3b0263d. This will update automatically on new commits. Configure here.

@binarybaron
Copy link
Copy Markdown
Author

bugbot run

@Einliterflasche
Copy link
Copy Markdown

Can't vouch for the migration, rest LGTM

@binarybaron binarybaron marked this pull request as ready for review March 31, 2026 16:21
@binarybaron
Copy link
Copy Markdown
Author

bugbot run

@binarybaron binarybaron merged commit 00d6c2d into master Mar 31, 2026
34 checks passed
Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

json_extract(state, '$.Bob.ExecutionSetupDone.state2.tx_punish_fee') AS tx_punish_fee,
json_extract(state, '$.Bob.ExecutionSetupDone.state2.punish_address') AS punish_address
FROM swap_states
WHERE json_extract(state, '$.Bob.ExecutionSetupDone') IS NOT NULL;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Migration references nonexistent JSON path in database

High Severity

The migration extracts source data from $.Bob.ExecutionSetupDone.state2.tx_punish_fee and $.Bob.ExecutionSetupDone.state2.punish_address, but the Rust enum variant is SwapSetupCompleted(State2) with no #[serde(rename = "ExecutionSetupDone")] attribute. A codebase-wide search finds zero references to ExecutionSetupDone outside this migration file. Additionally, SwapSetupCompleted(State2) is a tuple variant, so serde serializes it without a state2 wrapper key. If DB records use the current variant name, the migration finds no source rows, causing either silent no-ops or assertion failures that block the upgrade.

Fix in Cursor Fix in Web


// Attempt to refund. Reachable from both Cancel and Punish (if tx_punish has not yet been published).
let (tx_refund, refund_type) = state.construct_best_bitcoin_refund_tx().context("Couldn't construct best Bitcoin refund transaction").map_err(backoff::Error::transient)?;
bitcoin_wallet.ensure_broadcasted(tx_refund, &refund_type.to_string()).await.map_err(|e| backoff::Error::transient(e.context("Couldn't publish best refund transaction")))?;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Refund failure no longer detects punishment via error codes

Medium Severity

The old code detected punishment by matching specific RPC error codes (RpcVerifyRejected/RpcVerifyError) when the refund broadcast failed, transitioning to BtcPunished. The new code removes this fallback entirely — all ensure_broadcasted errors are now transient retries. If construct_tx_punish().id() doesn't match the on-chain punish tx (e.g., Alice used a different fee or address, since these aren't enforced on-chain), the refund keeps failing and the code retries indefinitely, never reaching BtcPunished.

Fix in Cursor Fix in Web

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