Skip to content

Sweep a colored output when its closing transaction's witness updates, whatever other witnesses fail - #208

Open
darkhorse-core wants to merge 3 commits into
UTEXO-Protocol:devfrom
darkhorse-core:fix/sweep-closing-witness-only
Open

darkhorse-core wants to merge 3 commits into
UTEXO-Protocol:devfrom
darkhorse-core:fix/sweep-closing-witness-only

Conversation

@darkhorse-core

@darkhorse-core darkhorse-core commented Oct 14, 2026 •

Copy link
Copy Markdown

Context

We run a node on signet against our own electrs. Its bitcoind runs with the default txindex=0, which electrs supports since 0.9. Two colored opens that were discarded before broadcast left the node-side outputs of three later closed channels unswept for two days; with this change the next retry swept all three and the RGB amounts settled.

Problem

RgbOutputSpender::spend_spendable_outputs (src/ldk.rs) calls update_witnesses(closing_height, [closing txid]) and gives up when update_res.failed is not empty. update_witnesses also goes over the other witnesses in the stock that are not Ignored, so one failing witness blocks the sweep even when the closing transaction updated fine. LDK retries every minute and fails the same way, and the node's BTC and RGB amount stay on the closing output.

It happens when both hold:

  1. The stock holds a witness that never reached the chain, such as the funding witness of a colored open discarded before broadcast (Event::DiscardFunding): it is upserted as Tentative, and handle_open_chan_fail does not archive it.
  2. The resolver does not recognize the indexer's "not found" for that transaction. bitcoind words the same case in two ways depending on txindex: No such mempool or blockchain transaction with it, No such mempool transaction. Use -txindex ... without it. The electrum resolver treats only the first as Unresolved, so the second becomes a ResolverIssue and lands in failed.

Sweeping a closing output should not depend on whether unrelated witnesses resolve. The txindex setting only decides whether this shows up; the witness that fails is one the node itself left behind in condition 1.

Fix

Fail the sweep only when the closing transaction's own witness failed to update.

This keeps sweeping working on any electrs setup, whatever its bitcoind's txindex setting, so node operators do not need to know that this setting matters here. Treating bitcoind's non-txindex message as Unresolved in the electrum resolver would also cover it, on the rgb-ops side.

Test

sweep_after_discarded_funding recreates bitcoind with -txindex=0 (optional BITCOIND_EXTRA_ARGS in compose.yaml), discards a colored open with DROP_FUNDING_SIGNED_ON_NODE, then opens and cooperatively closes another colored channel and waits for node1's output to be swept.

On master 7597bbd it fails without the fix and passes with it. Without the fix and with txindex on it passes, which is why the current suite does not catch this. dev's BFA dependencies are not public, so the test has not been compiled against dev; on this branch it uses open_channel_request_raw and the bitcoind() helper instead of the master equivalents.

@darkhorse-core

Copy link
Copy Markdown
Author

Added a test to reproduce this, details in the description.

@orbitalturtle orbitalturtle left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hi @darkhorse-core, thanks for the contribution & for adding a test that reproduces the issue! ACK from inspection that this is indeed an issue.

General feedback:

  • Because this impacts RGB-Tools' branch as well, it would be best to move this upstream. They might have further feedback on the fix approach as well
  • If you can spend a bit more time trying to convey the issue at a high-level next time, that might help with review. :) Took me a while to understand the bug. If using AI, asking it for a more succinct description can sometimes help. IIUC, it seems like the main points are:
    • To replicate this, must run electrs with txindex=0 (which is the default mode, but notably using this mode was not supported in the tests before his test addition)
    • If specifically a colored channel open fails... this leads to odd behavior. Every later colored channel close leaves the node's funds stuck on chain, for closes completely unrelated to that failed open.
    • The closing transaction confirms on chain as normal. What fails is the sweep afterward, where the node moves its BTC and RGB from the closing output back into its wallet. That step keeps failing on every retry, so the funds are stuck there, though not lost.
  • If you can squash the two fix commits into one, that also helps us with review. But keep the test in a separate commit

This branch has not been deployed

No deployments
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