What changed, and why it matters
This commit changes how Electrum decides whether a tiny ("dust") Lightning Network sweep output is worth including in a transaction. Previously, the code only skipped the dust check for anchor outputs. Now it uses a new explicit flag, `dust_override`, so other sweep types can also skip the dust check when appropriate. The change itself is a small refactor and does not obviously introduce a security bug, but it adjusts a safety check that prevents creating uneconomical or non-standard transactions.
Review the specific sweep types marked `dust_override=True` to confirm they are intentionally allowed to produce outputs below the dust limit, and verify that this does not lead to non-standard or uneconomical transactions being broadcast. Consider adding tests covering the new flag and edge cases around dust outputs.
Security signals we found
Change to dust-limit logic in transaction batching
New boolean override flag added to a core sweep data structure
Some non-anchor sweeps now bypass the dust check
No explicit security rationale or CVE reference in commit message
Evidence from the diff
The SweepInfo NamedTuple gains a dust_override: bool field. TxBatch.is_dust() now returns False (i.e., treats the sweep as non-dust) when dust_override is true, instead of only when sweep_info.is_anchor() is true. Call sites in lnsweep.py, submarine_swaps.py, and the test fixture set the flag explicitly: anchors and some to_remote/to_local sweeps set True, while HTLC sweeps and submarine swaps set False. This decouples the dust override from the anchor classification.
Changed components
electrum/txbatcher.pyelectrum/lnsweep.pyelectrum/submarine_swaps.pytests/test_txbatcher.pyInspect captured patch +15 / −1
diff --git a/electrum/lnsweep.py b/electrum/lnsweep.py
index f630641..014c87a 100644
--- a/electrum/lnsweep.py
+++ b/electrum/lnsweep.py
@@ -43,6 +43,7 @@ class SweepInfo(NamedTuple):
txin: PartialTxInput
txout: Optional[PartialTxOutput] # only for first-stage htlc tx
can_be_batched: bool # todo: this could be more fine-grained
+ dust_override: bool
def is_anchor(self):
return self.name in ['local_anchor', 'remote_anchor']
@@ -260,6 +261,7 @@ def sweep_their_htlctx_justice(
txin=txin,
txout=None,
can_be_batched=False,
+ dust_override=False,
)
return index_to_sweepinfo
@@ -337,6 +339,7 @@ def sweep_our_ctx(
txin=txin,
txout=None,
can_be_batched=True,
+ dust_override=True,
)
# to_local
@@ -358,6 +361,7 @@ def sweep_our_ctx(
txin=txin,
txout=None,
can_be_batched=True,
+ dust_override=False,
)
we_breached = ctn < chan.get_oldest_unrevoked_ctn(LOCAL)
if we_breached:
@@ -398,6 +402,7 @@ def sweep_our_ctx(
# - in particular, it would be safe to batch htlcs where
# htlc_direction, htlc.payment_hash, htlc.cltv_abs
# all match. That is, MPP htlcs for the same payment.
+ dust_override=False,
)
else:
# second-stage
@@ -420,6 +425,7 @@ def sweep_our_ctx(
# this is safe to batch, we are the only ones who can spend
# (assuming we did not broadcast a revoked state)
can_be_batched=True,
+ dust_override=False,
)
# offered HTLCs, in our ctx --> "timeout"
@@ -557,6 +563,7 @@ def sweep_their_ctx_to_remote_backup(
txin=txin,
txout=None,
can_be_batched=True,
+ dust_override=True,
)
# to_remote
@@ -577,6 +584,7 @@ def sweep_their_ctx_to_remote_backup(
txin=txin,
txout=None,
can_be_batched=True,
+ dust_override=False,
)
return txs
@@ -634,6 +642,7 @@ def sweep_their_ctx(
txin=txin,
txout=None,
can_be_batched=True,
+ dust_override=True,
)
# to_local is handled by lnwatcher
@@ -646,6 +655,7 @@ def sweep_their_ctx(
txin=txin,
txout=None,
can_be_batched=False,
+ dust_override=False,
)
# to_remote
@@ -676,6 +686,7 @@ def sweep_their_ctx(
txin=txin,
txout=None,
can_be_batched=True,
+ dust_override=False,
)
# HTLCs
@@ -715,6 +726,7 @@ def sweep_their_ctx(
txout=None,
can_be_batched=False, # both parties can spend
# (still, in some cases we could batch, see comment in sweep_our_ctx)
+ dust_override=False,
)
# received HTLCs, in their ctx --> "timeout"
# offered HTLCs, in their ctx --> "success"
diff --git a/electrum/submarine_swaps.py b/electrum/submarine_swaps.py
index b0b6230..dfc5ebf 100644
--- a/electrum/submarine_swaps.py
+++ b/electrum/submarine_swaps.py
@@ -529,6 +529,7 @@ class SwapManager(Logger):
txout=None,
name=name,
can_be_batched=can_be_batched,
+ dust_override=False,
)
try:
self.wallet.txbatcher.add_sweep_input('swaps', sweep_info)
diff --git a/electrum/txbatcher.py b/electrum/txbatcher.py
index 6451bfb..bd01832 100644
--- a/electrum/txbatcher.py
+++ b/electrum/txbatcher.py
@@ -265,7 +265,7 @@ class TxBatch(Logger):
def is_dust(self, sweep_info: SweepInfo) -> bool:
"""Can raise NoDynamicFeeEstimates."""
- if sweep_info.is_anchor():
+ if sweep_info.dust_override:
return False
if sweep_info.txout is not None:
return False
diff --git a/tests/test_txbatcher.py b/tests/test_txbatcher.py
index 2afafe4..f038bb7 100644
--- a/tests/test_txbatcher.py
+++ b/tests/test_txbatcher.py
@@ -86,6 +86,7 @@ SWAP_SWEEP_INFO = SweepInfo(
txout=None,
name='swap claim',
can_be_batched=True,
+ dust_override=False,
)
Why this scored 26/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.