Skip to content

Commit 46b1938

Browse files
committed
lnwatcher: catch exc during ctx output sweeping
Catch exceptions occuring during the sweep of a single ctx output and keep going sweeping the other outputs so we sweep at least those that do not crash and don't leave them on the table.
1 parent 85fa70e commit 46b1938

1 file changed

Lines changed: 63 additions & 47 deletions

File tree

electrum/lnwatcher.py

Lines changed: 63 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,13 @@
88
from . import util
99
from .util import (
1010
TxMinedInfo, BelowDustLimit, NoDynamicFeeEstimates, OldTaskGroup, EventListener, event_listener, log_exceptions,
11-
ignore_exceptions, now
11+
ignore_exceptions, now, send_exception_to_crash_reporter
1212
)
1313
from .transaction import Transaction, TxOutpoint
1414
from .logging import Logger
1515
from .address_synchronizer import TX_HEIGHT_LOCAL
1616
from .lnutil import REDEEM_AFTER_DOUBLE_SPENT_DELAY
17-
from .lnsweep import KeepWatchingTXO, SweepInfo
17+
from .lnsweep import KeepWatchingTXO, SweepInfo, MaybeSweepInfo
1818

1919
if TYPE_CHECKING:
2020
from .network import Network
@@ -201,58 +201,74 @@ async def sweep_commitment_transaction(self, funding_outpoint: str, closing_tx:
201201
chan = self.lnworker.channel_by_txo(funding_outpoint)
202202
if not chan:
203203
return False
204-
local_height = self.adb.get_local_height()
205204
self._pending_force_closes.pop(chan, None) # recomputed below
206205
# detect who closed and get information about how to claim outputs
207206
is_local_ctx, sweep_info_dict = chan.get_ctx_sweep_info(closing_tx)
208207
# note: we need to keep watching *at least* until the closing tx is deeply mined,
209208
# possibly longer if there are TXOs to sweep
210209
keep_watching = not self.adb.is_deeply_mined(closing_tx.txid())
211210
# create and broadcast transactions
212-
for prevout, sweep_info in sweep_info_dict.items(): # FIXME isolate iterations (error-wise)
213-
prev_txid, prev_index = prevout.split(':')
214-
name = sweep_info.name + ' ' + chan.get_id_for_log()
215-
self.lnworker.wallet.set_default_label(prevout, name)
216-
if isinstance(sweep_info, KeepWatchingTXO): # haven't yet decided if we want to sweep
217-
keep_watching |= sweep_info.until_height > local_height
218-
continue
219-
assert isinstance(sweep_info, SweepInfo), sweep_info
220-
if not self.adb.get_transaction(prev_txid):
221-
# do not keep watching if prevout does not exist
222-
self.logger.info(f'prevout does not exist for {name}: {prevout}')
223-
continue
224-
watch_sweep_info = self.maybe_redeem(sweep_info)
225-
spender_txid = self.adb.get_spender(prevout) # note: LOCAL spenders don't count
226-
spender_tx = self.adb.get_transaction(spender_txid) if spender_txid else None
227-
if spender_tx:
228-
# the spender might be the remote, revoked or not
229-
htlc_sweepinfo = chan.maybe_sweep_htlcs(closing_tx, spender_tx)
230-
if htlc_sweepinfo:
231-
self.adb.subscribe_to_outputs(spender_txid)
232-
for prevout2, htlc_sweep_info in htlc_sweepinfo.items():
233-
self.lnworker.wallet.set_default_label(prevout2, htlc_sweep_info.name)
234-
if isinstance(htlc_sweep_info, KeepWatchingTXO): # haven't yet decided if we want to sweep
235-
keep_watching |= htlc_sweep_info.until_height > local_height
236-
continue
237-
assert isinstance(htlc_sweep_info, SweepInfo), htlc_sweep_info
238-
watch_htlc_sweep_info = self.maybe_redeem(htlc_sweep_info)
239-
htlc_tx_spender = self.adb.get_spender(prevout2)
240-
if htlc_tx_spender:
241-
keep_watching |= not self.adb.is_deeply_mined(htlc_tx_spender)
242-
self.maybe_add_accounting_address(htlc_tx_spender, htlc_sweep_info)
243-
else:
244-
keep_watching |= watch_htlc_sweep_info
245-
keep_watching |= not self.adb.is_deeply_mined(spender_txid)
246-
self.maybe_extract_preimage(chan, spender_tx, prevout)
247-
self.maybe_add_accounting_address(spender_txid, sweep_info)
248-
else:
249-
keep_watching |= watch_sweep_info
250-
self.maybe_add_pending_forceclose(
251-
chan=chan,
252-
spender_txid=spender_txid,
253-
is_local_ctx=is_local_ctx,
254-
sweep_info=sweep_info,
255-
)
211+
for prevout, sweep_info in sweep_info_dict.items():
212+
try:
213+
keep_watching |= self._sweep_ctx_output(prevout, sweep_info, chan, closing_tx, is_local_ctx)
214+
except Exception as e:
215+
# in case a single sweep crashes we keep sweeping the other outputs
216+
send_exception_to_crash_reporter(e) # logs too
217+
keep_watching = True
218+
return keep_watching
219+
220+
def _sweep_ctx_output(
221+
self,
222+
prevout: str,
223+
sweep_info: MaybeSweepInfo,
224+
chan: 'AbstractChannel',
225+
closing_tx: Transaction,
226+
is_local_ctx: bool,
227+
) -> bool:
228+
keep_watching = False
229+
local_height = self.adb.get_local_height()
230+
prev_txid, prev_index = prevout.split(':')
231+
name = sweep_info.name + ' ' + chan.get_id_for_log()
232+
self.lnworker.wallet.set_default_label(prevout, name)
233+
if isinstance(sweep_info, KeepWatchingTXO): # haven't yet decided if we want to sweep
234+
return sweep_info.until_height > local_height
235+
assert isinstance(sweep_info, SweepInfo), sweep_info
236+
if not self.adb.get_transaction(prev_txid):
237+
# do not keep watching if prevout does not exist
238+
self.logger.info(f'prevout does not exist for {name}: {prevout}')
239+
return False
240+
watch_sweep_info = self.maybe_redeem(sweep_info)
241+
spender_txid = self.adb.get_spender(prevout) # note: LOCAL spenders don't count
242+
spender_tx = self.adb.get_transaction(spender_txid) if spender_txid else None
243+
if spender_tx:
244+
# the spender might be the remote, revoked or not
245+
htlc_sweepinfo = chan.maybe_sweep_htlcs(closing_tx, spender_tx)
246+
if htlc_sweepinfo:
247+
self.adb.subscribe_to_outputs(spender_txid)
248+
for prevout2, htlc_sweep_info in htlc_sweepinfo.items():
249+
self.lnworker.wallet.set_default_label(prevout2, htlc_sweep_info.name)
250+
if isinstance(htlc_sweep_info, KeepWatchingTXO): # haven't yet decided if we want to sweep
251+
keep_watching |= htlc_sweep_info.until_height > local_height
252+
continue
253+
assert isinstance(htlc_sweep_info, SweepInfo), htlc_sweep_info
254+
watch_htlc_sweep_info = self.maybe_redeem(htlc_sweep_info)
255+
htlc_tx_spender = self.adb.get_spender(prevout2)
256+
if htlc_tx_spender:
257+
keep_watching |= not self.adb.is_deeply_mined(htlc_tx_spender)
258+
self.maybe_add_accounting_address(htlc_tx_spender, htlc_sweep_info)
259+
else:
260+
keep_watching |= watch_htlc_sweep_info
261+
keep_watching |= not self.adb.is_deeply_mined(spender_txid)
262+
self.maybe_extract_preimage(chan, spender_tx, prevout)
263+
self.maybe_add_accounting_address(spender_txid, sweep_info)
264+
else:
265+
keep_watching |= watch_sweep_info
266+
self.maybe_add_pending_forceclose(
267+
chan=chan,
268+
spender_txid=spender_txid,
269+
is_local_ctx=is_local_ctx,
270+
sweep_info=sweep_info,
271+
)
256272
return keep_watching
257273

258274
def get_pending_force_closes(self) -> Dict['AbstractChannel', int]:

0 commit comments

Comments
 (0)