Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions electrum/lnchannel.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
received_htlc_trim_threshold_sat, make_commitment_output_to_remote_address, FIXED_ANCHOR_SAT,
ChannelType, LNProtocolWarning, ZEROCONF_TIMEOUT)
from .lnsweep import sweep_our_ctx, sweep_their_ctx
from .lnsweep import sweep_their_htlctx_justice, sweep_our_htlctx, SweepInfo
from .lnsweep import sweep_their_htlctx_justice, sweep_our_htlctx, SweepInfo, MaybeSweepInfo
from .lnsweep import sweep_their_ctx_to_remote_backup
from .lnhtlc import HTLCManager
from .lnmsg import encode_msg, decode_msg
Expand Down Expand Up @@ -286,10 +286,10 @@ def get_closing_height(self) -> Optional[Tuple[str, int, Optional[int]]]:
def delete_closing_height(self):
self.storage.pop('closing_height', None)

def create_sweeptxs_for_our_ctx(self, ctx: Transaction) -> Dict[str, SweepInfo]:
def create_sweeptxs_for_our_ctx(self, ctx: Transaction) -> Dict[str, MaybeSweepInfo]:
return sweep_our_ctx(chan=self, ctx=ctx)

def create_sweeptxs_for_their_ctx(self, ctx: Transaction) -> Dict[str, SweepInfo]:
def create_sweeptxs_for_their_ctx(self, ctx: Transaction) -> Dict[str, MaybeSweepInfo]:
return sweep_their_ctx(chan=self, ctx=ctx)

def is_backup(self) -> bool:
Expand All @@ -304,7 +304,7 @@ def get_remote_scid_alias(self) -> Optional[bytes]:
def get_remote_peer_sent_error(self) -> Optional[str]:
return None

def get_ctx_sweep_info(self, ctx: Transaction) -> Tuple[bool, Dict[str, SweepInfo]]:
def get_ctx_sweep_info(self, ctx: Transaction) -> Tuple[bool, Dict[str, MaybeSweepInfo]]:
our_sweep_info = self.create_sweeptxs_for_our_ctx(ctx)
their_sweep_info = self.create_sweeptxs_for_their_ctx(ctx)
if our_sweep_info:
Expand All @@ -327,7 +327,7 @@ def get_ctx_sweep_info(self, ctx: Transaction) -> Tuple[bool, Dict[str, SweepInf
is_local_ctx = who_closed == LOCAL
return is_local_ctx, sweep_info

def maybe_sweep_htlcs(self, ctx: Transaction, htlc_tx: Transaction) -> Dict[str, SweepInfo]:
def maybe_sweep_htlcs(self, ctx: Transaction, htlc_tx: Transaction) -> Dict[str, MaybeSweepInfo]:
return {}

def extract_preimage_from_htlc_txin(self, txin: TxInput, *, is_deeply_mined: bool) -> None:
Expand Down Expand Up @@ -682,7 +682,7 @@ def create_sweeptxs_for_our_ctx(self, ctx):
else:
return {}

def maybe_sweep_htlcs(self, ctx: Transaction, htlc_tx: Transaction) -> Dict[str, SweepInfo]:
def maybe_sweep_htlcs(self, ctx: Transaction, htlc_tx: Transaction) -> Dict[str, MaybeSweepInfo]:
return {}

def extract_preimage_from_htlc_txin(self, txin: TxInput, *, is_deeply_mined: bool) -> None:
Expand Down Expand Up @@ -1939,7 +1939,7 @@ def get_close_options(self) -> Sequence[ChanCloseOption]:
assert not (self.get_state() == ChannelState.WE_ARE_TOXIC and ChanCloseOption.LOCAL_FCLOSE in ret), "local force-close unsafe if we are toxic"
return ret

def maybe_sweep_htlcs(self, ctx: Transaction, htlc_tx: Transaction) -> Dict[str, SweepInfo]:
def maybe_sweep_htlcs(self, ctx: Transaction, htlc_tx: Transaction) -> Dict[str, MaybeSweepInfo]:
# look at the output address, check if it matches
d = sweep_their_htlctx_justice(self, ctx, htlc_tx)
d2 = sweep_our_htlctx(self, ctx, htlc_tx)
Expand Down
45 changes: 34 additions & 11 deletions electrum/lnsweep.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,15 @@ def csv_delay(self):
return self.txin.get_block_based_relative_locktime() or 0


class KeepWatchingTXO(NamedTuple):
"""Used for UTXOs we don't yet know if we want to sweep, such as pending HTLCs for JIT channels."""
name: str
until_height: int


MaybeSweepInfo = SweepInfo | KeepWatchingTXO


def sweep_their_ctx_watchtower(
chan: 'Channel',
ctx: Transaction,
Expand Down Expand Up @@ -282,7 +291,7 @@ def sweep_our_ctx(
*, chan: 'AbstractChannel',
ctx: Transaction,
actual_htlc_tx: Transaction=None, # if passed, return second stage htlcs
) -> Dict[str, SweepInfo]:
) -> Dict[str, MaybeSweepInfo]:

"""Handle the case where we force-close unilaterally with our latest ctx.

Expand Down Expand Up @@ -329,7 +338,7 @@ def sweep_our_ctx(
# other outputs are htlcs
# if they are spent, we need to generate the script
# so, second-stage htlc sweep should not be returned here
txs = {} # type: Dict[str, SweepInfo]
txs = {} # type: Dict[str, MaybeSweepInfo]

# local anchor
if actual_htlc_tx is None and chan.has_anchors():
Expand Down Expand Up @@ -444,9 +453,13 @@ def txs_htlc(
# note: it is the first stage (witness of htlc_tx) that reveals the preimage,
# so if we are already in second stage, it is already revealed.
# However, here, we don't make a distinction.
preimage = _maybe_reveal_preimage_for_htlc(
preimage, keep_watching_txo = _maybe_reveal_preimage_for_htlc(
chan=chan, htlc=htlc,
sweep_info_name=f"our_ctx_htlc_{ctx_output_idx}",
)
if keep_watching_txo:
prevout = ctx.txid() + ':%d' % ctx_output_idx
txs[prevout] = keep_watching_txo
if not preimage:
continue
try:
Expand All @@ -465,19 +478,25 @@ def _maybe_reveal_preimage_for_htlc(
*,
chan: 'AbstractChannel',
htlc: 'UpdateAddHtlc',
) -> Optional[bytes]:
sweep_info_name: str,
) -> Tuple[Optional[bytes], Optional[KeepWatchingTXO]]:
"""Given a Remote-added-HTLC, return the preimage if it's okay to reveal it on-chain."""
if not chan.lnworker.is_complete_mpp(htlc.payment_hash):
# - do not redeem this, it might publish the preimage of an incomplete MPP
# - OTOH maybe this chan just got closed, and we are still receiving new htlcs
# for this MPP set. So the MPP set might still transition to complete!
# The MPP_TIMEOUT is only around 2 minutes, so this window is short.
# The default keep_watching logic in lnwatcher is sufficient to call us again.
return None
if htlc.payment_hash in chan.lnworker.dont_settle_htlcs:
return None
return None, None
if htlc.payment_hash.hex() in chan.lnworker.dont_settle_htlcs:
# we should not reveal the preimage *for now*, but we might still decide to reveal it later
keep_watching_txo = KeepWatchingTXO(
name=sweep_info_name + "_dont_settle_htlcs",
until_height=htlc.cltv_abs,
)
return None, keep_watching_txo
preimage = chan.lnworker.get_preimage(htlc.payment_hash)
return preimage
return preimage, None


def extract_ctx_secrets(chan: 'Channel', ctx: Transaction):
Expand Down Expand Up @@ -614,7 +633,7 @@ def sweep_their_ctx_to_remote_backup(

def sweep_their_ctx(
*, chan: 'Channel',
ctx: Transaction) -> Optional[Dict[str, SweepInfo]]:
ctx: Transaction) -> Optional[Dict[str, MaybeSweepInfo]]:
"""Handle the case when the remote force-closes with their ctx.
Sweep outputs that do not have a CSV delay ('to_remote' and first-stage HTLCs).
Outputs with CSV delay ('to_local' and second-stage HTLCs) are redeemed by LNWatcher.
Expand All @@ -628,7 +647,7 @@ def sweep_their_ctx(

Outputs with CSV/CLTV are redeemed by LNWatcher.
"""
txs = {} # type: Dict[str, SweepInfo]
txs = {} # type: Dict[str, MaybeSweepInfo]
our_conf, their_conf = get_ordered_channel_configs(chan=chan, for_us=True)
x = extract_ctx_secrets(chan, ctx)
if not x:
Expand Down Expand Up @@ -761,9 +780,13 @@ def tx_htlc(
preimage = None
is_received_htlc = direction == RECEIVED
if not is_received_htlc and not is_revocation:
preimage = _maybe_reveal_preimage_for_htlc(
preimage, keep_watching_txo = _maybe_reveal_preimage_for_htlc(
chan=chan, htlc=htlc,
sweep_info_name=f"their_ctx_htlc_{ctx_output_idx}",
)
if keep_watching_txo:
prevout = ctx.txid() + ':%d' % ctx_output_idx
txs[prevout] = keep_watching_txo
if not preimage:
continue
tx_htlc(
Expand Down
14 changes: 11 additions & 3 deletions electrum/lnwatcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,10 @@
from .logging import Logger
from .address_synchronizer import TX_HEIGHT_LOCAL
from .lnutil import REDEEM_AFTER_DOUBLE_SPENT_DELAY

from .lnsweep import KeepWatchingTXO, SweepInfo

if TYPE_CHECKING:
from .network import Network
from .lnsweep import SweepInfo
from .lnworker import LNWallet
from .lnchannel import AbstractChannel

Expand Down Expand Up @@ -159,6 +158,7 @@ async def sweep_commitment_transaction(self, funding_outpoint: str, closing_tx:
chan = self.lnworker.channel_by_txo(funding_outpoint)
if not chan:
return False
local_height = self.adb.get_local_height()
# detect who closed and get information about how to claim outputs
is_local_ctx, sweep_info_dict = chan.get_ctx_sweep_info(closing_tx)
# note: we need to keep watching *at least* until the closing tx is deeply mined,
Expand All @@ -169,6 +169,10 @@ async def sweep_commitment_transaction(self, funding_outpoint: str, closing_tx:
prev_txid, prev_index = prevout.split(':')
name = sweep_info.name + ' ' + chan.get_id_for_log()
self.lnworker.wallet.set_default_label(prevout, name)
if isinstance(sweep_info, KeepWatchingTXO): # haven't yet decided if we want to sweep
keep_watching |= sweep_info.until_height > local_height
continue
assert isinstance(sweep_info, SweepInfo), sweep_info
if not self.adb.get_transaction(prev_txid):
# do not keep watching if prevout does not exist
self.logger.info(f'prevout does not exist for {name}: {prevout}')
Expand All @@ -180,9 +184,13 @@ async def sweep_commitment_transaction(self, funding_outpoint: str, closing_tx:
# the spender might be the remote, revoked or not
htlc_sweepinfo = chan.maybe_sweep_htlcs(closing_tx, spender_tx)
for prevout2, htlc_sweep_info in htlc_sweepinfo.items():
self.lnworker.wallet.set_default_label(prevout2, htlc_sweep_info.name)
if isinstance(htlc_sweep_info, KeepWatchingTXO): # haven't yet decided if we want to sweep
keep_watching |= htlc_sweep_info.until_height > local_height
continue
assert isinstance(htlc_sweep_info, SweepInfo), htlc_sweep_info
watch_htlc_sweep_info = self.maybe_redeem(htlc_sweep_info)
htlc_tx_spender = self.adb.get_spender(prevout2)
self.lnworker.wallet.set_default_label(prevout2, htlc_sweep_info.name)
if htlc_tx_spender:
keep_watching |= not self.adb.is_deeply_mined(htlc_tx_spender)
self.maybe_add_accounting_address(htlc_tx_spender, htlc_sweep_info)
Expand Down