URI: 
       tPerform breach remedy without sweepstore: - add functions to lnsweep - lnworker: analyze candidate ctx and htlc_tx - watchtower will be optional - add test for breach remedy with spent htlcs - save tx name as label - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
   DIR commit a8ce8109bea4210c821e1757f97d398a8c9103b4
   DIR parent 238f3c949ca2d23cbe4cc4e5a1ccd15371050663
  HTML Author: ThomasV <thomasv@electrum.org>
       Date:   Mon, 24 Jun 2019 11:13:18 +0200
       
       Perform breach remedy without sweepstore:
        - add functions to lnsweep
        - lnworker: analyze candidate ctx and htlc_tx
        - watchtower will be optional
        - add test for breach remedy with spent htlcs
        - save tx name as label
       
       Diffstat:
         M electrum/lnchannel.py               |      44 +++++++++++--------------------
         M electrum/lnpeer.py                  |      12 +++++++++---
         M electrum/lnsweep.py                 |     129 ++++++++++++++++++++++++++-----
         M electrum/lnutil.py                  |       2 +-
         M electrum/lnwatcher.py               |       9 ++++-----
         M electrum/lnworker.py                |      89 +++++++++++++++++++------------
         M electrum/tests/regtest/regtest.sh   |      83 ++++++++++++++++++++++++++++++-
         M electrum/tests/test_regtest.py      |       7 +++++--
       
       8 files changed, 280 insertions(+), 95 deletions(-)
       ---
   DIR diff --git a/electrum/lnchannel.py b/electrum/lnchannel.py
       t@@ -46,7 +46,8 @@ from .lnutil import (Outpoint, LocalConfig, RemoteConfig, Keypair, OnlyPubkeyKey
                            HTLC_TIMEOUT_WEIGHT, HTLC_SUCCESS_WEIGHT, extract_ctn_from_tx_and_chan, UpdateAddHtlc,
                            funding_output_script, SENT, RECEIVED, LOCAL, REMOTE, HTLCOwner, make_commitment_outputs,
                            ScriptHtlc, PaymentFailure, calc_onchain_fees, RemoteMisbehaving, make_htlc_output_witness_script)
       -from .lnsweep import create_sweeptxs_for_their_revoked_ctx, create_sweeptxs_for_our_ctx, create_sweeptxs_for_their_ctx
       +from .lnsweep import create_sweeptxs_for_our_ctx, create_sweeptxs_for_their_ctx
       +from .lnsweep import create_sweeptx_for_their_revoked_htlc
        from .lnhtlc import HTLCManager
        
        
       t@@ -165,7 +166,7 @@ class Channel(Logger):
                self.set_state('DISCONNECTED')
                self.local_commitment = None
                self.remote_commitment = None
       -        self.sweep_info = None
       +        self.sweep_info = {}
        
            def get_payments(self):
                out = {}
       t@@ -450,12 +451,6 @@ class Channel(Logger):
                point = secret_to_pubkey(int.from_bytes(secret, 'big'))
                return secret, point
        
       -    def process_new_revocation_secret(self, per_commitment_secret: bytes):
       -        outpoint = self.funding_outpoint.to_str()
       -        ctx = self.remote_commitment_to_be_revoked  # FIXME can't we just reconstruct it?
       -        sweeptxs = create_sweeptxs_for_their_revoked_ctx(self, ctx, per_commitment_secret, self.sweep_address)
       -        return sweeptxs
       -
            def receive_revocation(self, revocation: RevokeAndAck):
                self.logger.info("receive_revocation")
        
       t@@ -469,16 +464,7 @@ class Channel(Logger):
                # this might break
                prev_remote_commitment = self.pending_commitment(REMOTE)
                self.config[REMOTE].revocation_store.add_next_entry(revocation.per_commitment_secret)
       -
       -        # be robust to exceptions raised in lnwatcher
       -        try:
       -            sweeptxs = self.process_new_revocation_secret(revocation.per_commitment_secret)
       -        except Exception as e:
       -            self.logger.info("Could not process revocation secret: {}".format(repr(e)))
       -            sweeptxs = []
       -
                ##### start applying fee/htlc changes
       -
                if self.pending_fee is not None:
                    if not self.constraints.is_initiator:
                        self.pending_fee[FUNDEE_SIGNED] = True
       t@@ -501,8 +487,6 @@ class Channel(Logger):
        
                self.set_remote_commitment()
                self.remote_commitment_to_be_revoked = prev_remote_commitment
       -        # return sweep transactions for watchtower
       -        return sweeptxs
        
            def balance(self, whose, *, ctx_owner=HTLCOwner.LOCAL, ctn=None):
                """
       t@@ -810,19 +794,23 @@ class Channel(Logger):
                assert tx.is_complete()
                return tx
        
       -    def get_sweep_info(self, ctx: Transaction):
       -        if self.sweep_info is None:
       +    def sweep_ctx(self, ctx: Transaction):
       +        txid = ctx.txid()
       +        if self.sweep_info.get(txid) is None:
                    ctn = extract_ctn_from_tx_and_chan(ctx, self)
                    our_sweep_info = create_sweeptxs_for_our_ctx(self, ctx, ctn, self.sweep_address)
                    their_sweep_info = create_sweeptxs_for_their_ctx(self, ctx, ctn, self.sweep_address)
       -            if our_sweep_info:
       -                self.sweep_info = our_sweep_info
       +            if our_sweep_info is not None:
       +                self.sweep_info[txid] = our_sweep_info
                        self.logger.info(f'we force closed.')
       -            elif their_sweep_info:
       -                self.sweep_info = their_sweep_info
       +            elif their_sweep_info is not None:
       +                self.sweep_info[txid] = their_sweep_info
                        self.logger.info(f'they force closed.')
                    else:
       -                self.sweep_info = {}
       +                self.sweep_info[txid] = {}
                        self.logger.info(f'not sure who closed {ctx}.')
       -            self.logger.info(f'{repr(self.sweep_info)}')
       -        return self.sweep_info
       +        return self.sweep_info[txid]
       +
       +    def sweep_htlc(self, ctx:Transaction, htlc_tx: Transaction):
       +        # look at the output address, check if it matches
       +        return create_sweeptx_for_their_revoked_htlc(self, ctx, htlc_tx, self.sweep_address)
   DIR diff --git a/electrum/lnpeer.py b/electrum/lnpeer.py
       t@@ -40,6 +40,7 @@ from .lnutil import (Outpoint, LocalConfig, RECEIVED, UpdateAddHtlc,
                             LightningPeerConnectionClosed, HandshakeFailed, NotFoundChanAnnouncementForUpdate,
                             MINIMUM_MAX_HTLC_VALUE_IN_FLIGHT_ACCEPTED, MAXIMUM_HTLC_MINIMUM_MSAT_ACCEPTED,
                             MAXIMUM_REMOTE_TO_SELF_DELAY_ACCEPTED, RemoteMisbehaving, DEFAULT_TO_SELF_DELAY)
       +from .lnsweep import create_sweeptxs_for_watchtower
        from .lntransport import LNTransport, LNTransportBase
        from .lnmsg import encode_msg, decode_msg
        from .interface import GracefulDisconnect
       t@@ -1261,15 +1262,20 @@ class Peer(Logger):
                self.logger.info("on_revoke_and_ack")
                channel_id = payload["channel_id"]
                chan = self.channels[channel_id]
       -        sweeptxs = chan.receive_revocation(RevokeAndAck(payload["per_commitment_secret"], payload["next_per_commitment_point"]))
       +        ctx = chan.remote_commitment_to_be_revoked  # FIXME can't we just reconstruct it?
       +        rev = RevokeAndAck(payload["per_commitment_secret"], payload["next_per_commitment_point"])
       +        chan.receive_revocation(rev)
                self._remote_changed_events[chan.channel_id].set()
                self._remote_changed_events[chan.channel_id].clear()
                self.lnworker.save_channel(chan)
                self.maybe_send_commitment(chan)
       -        asyncio.ensure_future(self._on_revoke_and_ack(chan, sweeptxs))
       +        asyncio.ensure_future(self._on_revoke_and_ack(chan, ctx, rev.per_commitment_secret))
        
       -    async def _on_revoke_and_ack(self, chan, sweeptxs):
       +    @ignore_exceptions
       +    @log_exceptions
       +    async def _on_revoke_and_ack(self, chan, ctx, per_commitment_secret):
                outpoint = chan.funding_outpoint.to_str()
       +        sweeptxs = create_sweeptxs_for_watchtower(chan, ctx, per_commitment_secret, chan.sweep_address)
                for tx in sweeptxs:
                    await self.lnwatcher.add_sweep_tx(outpoint, tx.prevout(0), str(tx))
        
   DIR diff --git a/electrum/lnsweep.py b/electrum/lnsweep.py
       t@@ -27,8 +27,8 @@ _logger = get_logger(__name__)
        
        
        
       -def create_sweeptxs_for_their_revoked_ctx(chan: 'Channel', ctx: Transaction, per_commitment_secret: bytes,
       -                                          sweep_address: str) -> Dict[str,Transaction]:
       +def create_sweeptxs_for_watchtower(chan: 'Channel', ctx: Transaction, per_commitment_secret: bytes,
       +                                   sweep_address: str) -> Dict[str,Transaction]:
            """Presign sweeping transactions using the just received revoked pcs.
            These will only be utilised if the remote breaches.
            Sweep 'to_local', and all the HTLCs (two cases: directly from ctx, or from HTLC tx).
       t@@ -75,8 +75,9 @@ def create_sweeptxs_for_their_revoked_ctx(chan: 'Channel', ctx: Transaction, per
                    sweep_address=sweep_address,
                    privkey=other_revocation_privkey,
                    is_revocation=True)
       +
            ctn = extract_ctn_from_tx_and_chan(ctx, chan)
       -    assert ctn == chan.config[REMOTE].ctn
       +    assert ctn == chan.config[REMOTE].ctn - 1
            # received HTLCs, in their ctx
            received_htlcs = chan.included_htlcs(REMOTE, RECEIVED, ctn)
            for htlc in received_htlcs:
       t@@ -92,6 +93,68 @@ def create_sweeptxs_for_their_revoked_ctx(chan: 'Channel', ctx: Transaction, per
            return txs
        
        
       +def create_sweeptx_for_their_revoked_ctx(chan: 'Channel', ctx: Transaction, per_commitment_secret: bytes,
       +                                         sweep_address: str) -> Dict[str,Transaction]:
       +    # prep
       +    pcp = ecc.ECPrivkey(per_commitment_secret).get_public_key_bytes(compressed=True)
       +    this_conf, other_conf = get_ordered_channel_configs(chan=chan, for_us=False)
       +    other_revocation_privkey = derive_blinded_privkey(other_conf.revocation_basepoint.privkey,
       +                                                      per_commitment_secret)
       +    to_self_delay = other_conf.to_self_delay
       +    this_delayed_pubkey = derive_pubkey(this_conf.delayed_basepoint.pubkey, pcp)
       +    txs = []
       +    # to_local
       +    revocation_pubkey = ecc.ECPrivkey(other_revocation_privkey).get_public_key_bytes(compressed=True)
       +    witness_script = bh2u(make_commitment_output_to_local_witness_script(
       +        revocation_pubkey, to_self_delay, this_delayed_pubkey))
       +    to_local_address = redeem_script_to_address('p2wsh', witness_script)
       +    output_idx = ctx.get_output_idx_from_address(to_local_address)
       +    if output_idx is not None:
       +        sweep_tx = lambda: create_sweeptx_ctx_to_local(
       +            sweep_address=sweep_address,
       +            ctx=ctx,
       +            output_idx=output_idx,
       +            witness_script=witness_script,
       +            privkey=other_revocation_privkey,
       +            is_revocation=True)
       +        return sweep_tx
       +
       +def create_sweeptx_for_their_revoked_htlc(chan: 'Channel', ctx: Transaction, htlc_tx: Transaction,
       +                                          sweep_address: str) -> Dict[str,Transaction]:
       +    x = analyze_ctx(chan, ctx)
       +    if not x:
       +        return
       +    ctn, their_pcp, is_revocation, per_commitment_secret = x
       +    if not is_revocation:
       +        return
       +    # prep
       +    pcp = ecc.ECPrivkey(per_commitment_secret).get_public_key_bytes(compressed=True)
       +    this_conf, other_conf = get_ordered_channel_configs(chan=chan, for_us=False)
       +    other_revocation_privkey = derive_blinded_privkey(other_conf.revocation_basepoint.privkey,
       +                                                      per_commitment_secret)
       +    to_self_delay = other_conf.to_self_delay
       +    this_delayed_pubkey = derive_pubkey(this_conf.delayed_basepoint.pubkey, pcp)
       +    # same witness script as to_local
       +    revocation_pubkey = ecc.ECPrivkey(other_revocation_privkey).get_public_key_bytes(compressed=True)
       +    witness_script = bh2u(make_commitment_output_to_local_witness_script(
       +        revocation_pubkey, to_self_delay, this_delayed_pubkey))
       +    htlc_address = redeem_script_to_address('p2wsh', witness_script)
       +    # check that htlc_tx is a htlc
       +    if htlc_tx.outputs()[0].address != htlc_address:
       +        return
       +
       +    gen_tx = lambda: create_sweeptx_ctx_to_local(
       +        sweep_address=sweep_address,
       +        ctx=htlc_tx,
       +        output_idx=0,
       +        witness_script=witness_script,
       +        privkey=other_revocation_privkey,
       +        is_revocation=True)
       +
       +    return 'redeem_htlc2', 0, 0, gen_tx
       +
       +
       +
        def create_sweeptxs_for_our_ctx(chan: 'Channel', ctx: Transaction, ctn: int,
                                        sweep_address: str) -> Dict[str,Transaction]:
            """Handle the case where we force close unilaterally with our latest ctx.
       t@@ -99,6 +162,7 @@ def create_sweeptxs_for_our_ctx(chan: 'Channel', ctx: Transaction, ctn: int,
            'to_local' can be swept even if this is a breach (by us),
            but HTLCs cannot (old HTLCs are no longer stored).
            """
       +    ctn = extract_ctn_from_tx_and_chan(ctx, chan)
            our_conf, their_conf = get_ordered_channel_configs(chan=chan, for_us=True)
            our_per_commitment_secret = get_per_commitment_secret_from_seed(
                our_conf.per_commitment_secret_seed, RevocationStore.START_INDEX - ctn)
       t@@ -116,12 +180,18 @@ def create_sweeptxs_for_our_ctx(chan: 'Channel', ctx: Transaction, ctn: int,
            to_local_address = redeem_script_to_address('p2wsh', to_local_witness_script)
            their_payment_pubkey = derive_pubkey(their_conf.payment_basepoint.pubkey, our_pcp)
            to_remote_address = make_commitment_output_to_remote_address(their_payment_pubkey)
       -
            # test ctx
       +    _logger.debug(f'testing our ctx: {to_local_address} {to_remote_address}')
            if ctx.get_output_idx_from_address(to_local_address) is None\
               and ctx.get_output_idx_from_address(to_remote_address) is None:
                return
       -
       +    # we have to_local, to_remote.
       +    # other outputs are htlcs
       +    # if they are spent, we need to generate the script
       +    # so, second-stage htlc sweep should not be returned here
       +    if ctn != our_conf.ctn:
       +        _logger.info("we breached.")
       +        return {}
            txs = {}
            # to_local
            output_idx = ctx.get_output_idx_from_address(to_local_address)
       t@@ -155,7 +225,7 @@ def create_sweeptxs_for_our_ctx(chan: 'Channel', ctx: Transaction, ctn: int,
                    preimage=preimage,
                    is_received_htlc=is_received_htlc)
                sweep_tx = lambda: create_sweeptx_that_spends_htlctx_that_spends_htlc_in_ctx(
       -            'sweep_from_our_ctx_htlc_',
       +            'our_ctx_htlc_',
                    to_self_delay=to_self_delay,
                    htlc_tx=htlc_tx,
                    htlctx_witness_script=htlctx_witness_script,
       t@@ -165,6 +235,7 @@ def create_sweeptxs_for_our_ctx(chan: 'Channel', ctx: Transaction, ctn: int,
                # side effect
                txs[htlc_tx.prevout(0)] = ('first-stage-htlc', 0, htlc_tx.cltv_expiry, lambda: htlc_tx)
                txs[htlc_tx.txid() + ':0'] = ('second-stage-htlc', to_self_delay, 0, sweep_tx)
       +
            # offered HTLCs, in our ctx --> "timeout"
            # received HTLCs, in our ctx --> "success"
            offered_htlcs = chan.included_htlcs(LOCAL, SENT, ctn)  # type: List[UpdateAddHtlc]
       t@@ -175,17 +246,11 @@ def create_sweeptxs_for_our_ctx(chan: 'Channel', ctx: Transaction, ctn: int,
                create_txns_for_htlc(htlc, is_received_htlc=True)
            return txs
        
       -
       -def create_sweeptxs_for_their_ctx(chan: 'Channel', ctx: Transaction, ctn: int,
       -                                  sweep_address: str) -> Dict[str,Transaction]:
       -    """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.
       -    """
       -    our_conf, their_conf = get_ordered_channel_configs(chan=chan, for_us=True)
       -    ctn = extract_ctn_from_tx_and_chan(ctx, chan)
       +def analyze_ctx(chan, ctx):
            # note: the remote sometimes has two valid non-revoked commitment transactions,
            # either of which could be broadcast (their_conf.ctn, their_conf.ctn+1)
       +    our_conf, their_conf = get_ordered_channel_configs(chan=chan, for_us=True)
       +    ctn = extract_ctn_from_tx_and_chan(ctx, chan)
            per_commitment_secret = None
            if ctn == their_conf.ctn:
                their_pcp = their_conf.current_per_commitment_point
       t@@ -200,9 +265,23 @@ def create_sweeptxs_for_their_ctx(chan: 'Channel', ctx: Transaction, ctn: int,
                    return
                their_pcp = ecc.ECPrivkey(per_commitment_secret).get_public_key_bytes(compressed=True)
                is_revocation = True
       -        our_revocation_privkey = derive_blinded_privkey(our_conf.revocation_basepoint.privkey, per_commitment_secret)
       +        #_logger.info(f'tx for revoked: {list(txs.keys())}')
            else:
                return
       +    return ctn, their_pcp, is_revocation, per_commitment_secret
       +
       +def create_sweeptxs_for_their_ctx(chan: 'Channel', ctx: Transaction, ctn: int,
       +                                  sweep_address: str) -> Dict[str,Transaction]:
       +    """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.
       +    """
       +    txs = {}
       +    our_conf, their_conf = get_ordered_channel_configs(chan=chan, for_us=True)
       +    x = analyze_ctx(chan, ctx)
       +    if not x:
       +        return
       +    ctn, their_pcp, is_revocation, per_commitment_secret = x
            # to_local and to_remote addresses
            our_revocation_pubkey = derive_blinded_pubkey(our_conf.revocation_basepoint.pubkey, their_pcp)
            their_delayed_pubkey = derive_pubkey(their_conf.delayed_basepoint.pubkey, their_pcp)
       t@@ -211,10 +290,18 @@ def create_sweeptxs_for_their_ctx(chan: 'Channel', ctx: Transaction, ctn: int,
            to_local_address = redeem_script_to_address('p2wsh', witness_script)
            our_payment_pubkey = derive_pubkey(our_conf.payment_basepoint.pubkey, their_pcp)
            to_remote_address = make_commitment_output_to_remote_address(our_payment_pubkey)
       -    # test ctx
       +    # test if this is their ctx
       +    _logger.debug(f'testing their ctx: {to_local_address} {to_remote_address}')
            if ctx.get_output_idx_from_address(to_local_address) is None \
               and ctx.get_output_idx_from_address(to_remote_address) is None:
                return
       +
       +    if is_revocation:
       +        our_revocation_privkey = derive_blinded_privkey(our_conf.revocation_basepoint.privkey, per_commitment_secret)
       +        gen_tx = create_sweeptx_for_their_revoked_ctx(chan, ctx, per_commitment_secret, chan.sweep_address)
       +        if gen_tx:
       +            tx = gen_tx()
       +            txs[tx.prevout(0)] = ('to_local_for_revoked_ctx', 0, 0, gen_tx)
            # prep
            our_htlc_privkey = derive_privkey(secret=int.from_bytes(our_conf.htlc_basepoint.privkey, 'big'), per_commitment_point=their_pcp)
            our_htlc_privkey = ecc.ECPrivkey.from_secret_scalar(our_htlc_privkey)
       t@@ -223,7 +310,6 @@ def create_sweeptxs_for_their_ctx(chan: 'Channel', ctx: Transaction, ctn: int,
            our_payment_privkey = derive_privkey(our_payment_bp_privkey.secret_scalar, their_pcp)
            our_payment_privkey = ecc.ECPrivkey.from_secret_scalar(our_payment_privkey)
            assert our_payment_pubkey == our_payment_privkey.get_public_key_bytes(compressed=True)
       -    txs = {}
            # to_local is handled by lnwatcher
            # to_remote
            output_idx = ctx.get_output_idx_from_address(to_remote_address)
       t@@ -268,7 +354,7 @@ def create_sweeptxs_for_their_ctx(chan: 'Channel', ctx: Transaction, ctn: int,
                        privkey=our_revocation_privkey if is_revocation else our_htlc_privkey.get_secret_bytes(),
                        is_revocation=is_revocation,
                        cltv_expiry=cltv_expiry)
       -            name = f'their_ctx_sweep_htlc_{ctx.txid()[:8]}_{output_idx}'
       +            name = f'their_ctx_htlc_{output_idx}'
                    txs[prevout] = (name, 0, cltv_expiry, sweep_tx)
            # received HTLCs, in their ctx --> "timeout"
            received_htlcs = chan.included_htlcs(REMOTE, RECEIVED, ctn=ctn)  # type: List[UpdateAddHtlc]
       t@@ -327,7 +413,7 @@ def create_sweeptx_their_ctx_htlc(ctx: Transaction, witness_script: bytes, sweep
            if outvalue <= dust_threshold(): return None
            sweep_outputs = [TxOutput(TYPE_ADDRESS, sweep_address, outvalue)]
            tx = Transaction.from_io(sweep_inputs, sweep_outputs, version=2
       -            , name=f'their_ctx_sweep_htlc_{ctx.txid()[:8]}_{output_idx}'
       +            , name=f'their_ctx_htlc_{output_idx}'
                    # note that cltv_expiry, and therefore also locktime will be zero when breach!
                    , cltv_expiry=cltv_expiry, locktime=cltv_expiry)
            sig = bfh(tx.sign_txin(0, privkey))
       t@@ -431,7 +517,8 @@ def create_sweeptx_that_spends_htlctx_that_spends_htlc_in_ctx(
            outvalue = val - fee
            if outvalue <= dust_threshold(): return None
            sweep_outputs = [TxOutput(TYPE_ADDRESS, sweep_address, outvalue)]
       -    tx = Transaction.from_io(sweep_inputs, sweep_outputs, version=2, name=name_prefix + htlc_tx.txid(), csv_delay=to_self_delay)
       +    name = name_prefix + htlc_tx.txid()[0:4]
       +    tx = Transaction.from_io(sweep_inputs, sweep_outputs, version=2, name=name, csv_delay=to_self_delay)
        
            sig = bfh(tx.sign_txin(0, privkey))
            witness = construct_witness([sig, int(is_revocation), htlctx_witness_script])
   DIR diff --git a/electrum/lnutil.py b/electrum/lnutil.py
       t@@ -359,7 +359,7 @@ def make_htlc_tx_with_open_channel(chan: 'Channel', pcp: bytes, for_us: bool,
            is_htlc_success = for_us == we_receive
            script, htlc_tx_output = make_htlc_tx_output(
                amount_msat = amount_msat,
       -        local_feerate = chan.pending_feerate(LOCAL if for_us else REMOTE),
       +        local_feerate = chan.pending_feerate(LOCAL if for_us else REMOTE), # uses pending feerate..
                revocationpubkey=other_revocation_pubkey,
                local_delayedpubkey=delayedpubkey,
                success = is_htlc_success,
   DIR diff --git a/electrum/lnwatcher.py b/electrum/lnwatcher.py
       t@@ -248,7 +248,7 @@ class LNWatcher(AddressSynchronizer):
                    self.network.trigger_callback('channel_closed', funding_outpoint, spenders,
                                                  funding_txid, funding_height, closing_txid,
                                                  closing_height, closing_tx)  # FIXME sooo many args..
       -            await self.do_breach_remedy(funding_outpoint, spenders)
       +            #await self.do_breach_remedy(funding_outpoint, spenders)
                if not keep_watching:
                    await self.unwatch_channel(address, funding_outpoint)
                else:
       t@@ -289,8 +289,7 @@ class LNWatcher(AddressSynchronizer):
                        continue
                    sweep_txns = await self.sweepstore.get_sweep_tx(funding_outpoint, prevout)
                    for tx in sweep_txns:
       -                if not await self.broadcast_or_log(funding_outpoint, tx):
       -                    self.logger.info(f'{tx.name} could not publish tx: {str(tx)}, prevout: {prevout}')
       +                await self.broadcast_or_log(funding_outpoint, tx)
        
            async def broadcast_or_log(self, funding_outpoint, tx):
                height = self.get_tx_height(tx.txid()).height
       t@@ -299,9 +298,9 @@ class LNWatcher(AddressSynchronizer):
                try:
                    txid = await self.network.broadcast_transaction(tx)
                except Exception as e:
       -            self.logger.info(f'broadcast: {tx.name}: failure: {repr(e)}')
       +            self.logger.info(f'broadcast failure: {tx.name}: {repr(e)}')
                else:
       -            self.logger.info(f'broadcast: {tx.name}: success. txid: {txid}')
       +            self.logger.info(f'broadcast success: {tx.name}')
                    if funding_outpoint in self.tx_progress:
                        await self.tx_progress[funding_outpoint].tx_queue.put(tx)
                    return txid
   DIR diff --git a/electrum/lnworker.py b/electrum/lnworker.py
       t@@ -514,45 +514,66 @@ class LNWallet(LNWorker):
                # remove from channel_db
                if chan.short_channel_id is not None:
                    self.channel_db.remove_channel(chan.short_channel_id)
       -
                # detect who closed and set sweep_info
       -        sweep_info = chan.get_sweep_info(closing_tx)
       -
       +        sweep_info = chan.sweep_ctx(closing_tx)
                # create and broadcast transaction
                for prevout, e_tx in sweep_info.items():
                    name, csv_delay, cltv_expiry, gen_tx = e_tx
       -            if spenders.get(prevout) is not None:
       -                self.logger.info(f'outpoint already spent {prevout}')
       -                continue
       -            prev_txid, prev_index = prevout.split(':')
       -            broadcast = True
       -            if cltv_expiry:
       -                local_height = self.network.get_local_height()
       -                remaining = cltv_expiry - local_height
       -                if remaining > 0:
       -                    self.logger.info('waiting for {}: CLTV ({} > {}), funding outpoint {} and tx {}'
       -                                     .format(name, local_height, cltv_expiry, funding_outpoint[:8], prev_txid[:8]))
       -                    broadcast = False
       -            if csv_delay:
       -                prev_height = self.network.lnwatcher.get_tx_height(prev_txid)
       -                remaining = csv_delay - prev_height.conf
       -                if remaining > 0:
       -                    self.logger.info('waiting for {}: CSV ({} >= {}), funding outpoint {} and tx {}'
       -                                     .format(name, prev_height.conf, csv_delay, funding_outpoint[:8], prev_txid[:8]))
       -                    broadcast = False
       -            tx = gen_tx()
       -            if tx is None:
       -                self.logger.info(f'{name} could not claim output: {prevout}, dust')
       -            if broadcast:
       -                if not await self.network.lnwatcher.broadcast_or_log(funding_outpoint, tx):
       -                    self.logger.info(f'{name} could not publish encumbered tx: {str(tx)}, prevout: {prevout}')
       +            spender = spenders.get(prevout)
       +            if spender is not None:
       +                spender_tx = await self.network.get_transaction(spender)
       +                spender_tx = Transaction(spender_tx)
       +                e_htlc_tx = chan.sweep_htlc(closing_tx, spender_tx)
       +                if e_htlc_tx:
       +                    spender2 = spenders.get(spender_tx.outputs()[0])
       +                    if spender2:
       +                        self.logger.info(f'htlc is already spent {name}: {prevout}')
       +                    else:
       +                        self.logger.info(f'trying to redeem htlc {name}: {prevout}')
       +                        await self.try_redeem(spender+':0', e_htlc_tx)
       +                else:
       +                    self.logger.info(f'outpoint already spent {name}: {prevout}')
                    else:
       -                # it's OK to add local transaction, the fee will be recomputed
       -                try:
       -                    self.wallet.add_future_tx(tx, remaining)
       -                    self.logger.info(f'adding future tx: {name}. prevout: {prevout}')
       -                except Exception as e:
       -                    self.logger.info(f'could not add future tx: {name}. prevout: {prevout} {str(e)}')
       +                self.logger.info(f'trying to redeem {name}: {prevout}')
       +                await self.try_redeem(prevout, e_tx)
       +
       +    @log_exceptions
       +    async def try_redeem(self, prevout, e_tx):
       +        name, csv_delay, cltv_expiry, gen_tx = e_tx
       +        prev_txid, prev_index = prevout.split(':')
       +        broadcast = True
       +        if cltv_expiry:
       +            local_height = self.network.get_local_height()
       +            remaining = cltv_expiry - local_height
       +            if remaining > 0:
       +                self.logger.info('waiting for {}: CLTV ({} > {}), prevout {}'
       +                                 .format(name, local_height, cltv_expiry, prevout))
       +                broadcast = False
       +        if csv_delay:
       +            prev_height = self.network.lnwatcher.get_tx_height(prev_txid)
       +            remaining = csv_delay - prev_height.conf
       +            if remaining > 0:
       +                self.logger.info('waiting for {}: CSV ({} >= {}), prevout: {}'
       +                                 .format(name, prev_height.conf, csv_delay, prevout))
       +                broadcast = False
       +        tx = gen_tx()
       +        self.wallet.set_label(tx.txid(), name)
       +        if tx is None:
       +            self.logger.info(f'{name} could not claim output: {prevout}, dust')
       +        if broadcast:
       +            try:
       +                await self.network.broadcast_transaction(tx)
       +            except Exception as e:
       +                self.logger.info(f'could NOT publish {name} for prevout: {prevout}, {str(e)}')
       +            else:
       +                self.logger.info(f'success: broadcasting {name} for prevout: {prevout}')
       +        else:
       +            # it's OK to add local transaction, the fee will be recomputed
       +            try:
       +                self.wallet.add_future_tx(tx, remaining)
       +                self.logger.info(f'adding future tx: {name}. prevout: {prevout}')
       +            except Exception as e:
       +                self.logger.info(f'could not add future tx: {name}. prevout: {prevout} {str(e)}')
        
            def is_dangerous(self, chan):
                for x in chan.get_unfulfilled_htlcs():
   DIR diff --git a/electrum/tests/regtest/regtest.sh b/electrum/tests/regtest/regtest.sh
       t@@ -157,7 +157,7 @@ if [[ $1 == "redeem_htlcs" ]]; then
        fi
        
        
       -if [[ $1 == "breach_with_htlc" ]]; then
       +if [[ $1 == "breach_with_unspent_htlc" ]]; then
            $bob daemon stop
            ELECTRUM_DEBUG_LIGHTNING_SETTLE_DELAY=3 $bob daemon -s 127.0.0.1:51001:t start
            $bob daemon load_wallet
       t@@ -218,3 +218,84 @@ if [[ $1 == "breach_with_htlc" ]]; then
                exit 1
            fi
        fi
       +
       +
       +if [[ $1 == "breach_with_spent_htlc" ]]; then
       +    $bob daemon stop
       +    ELECTRUM_DEBUG_LIGHTNING_SETTLE_DELAY=3 $bob daemon -s 127.0.0.1:51001:t start
       +    $bob daemon load_wallet
       +    while alice_balance=$($alice getbalance | jq '.confirmed' | tr -d '"') && [ $alice_balance != "1" ]; do
       +        echo "waiting for alice balance"
       +        sleep 1
       +    done
       +    echo "alice opens channel"
       +    bob_node=$($bob nodeid)
       +    channel=$($alice open_channel $bob_node 0.15)
       +    new_blocks 3
       +    channel_state=""
       +    while channel_state=$($alice list_channels | jq '.[] | .state' | tr -d '"') && [ $channel_state != "OPEN" ]; do
       +        echo "waiting for channel open"
       +        sleep 1
       +    done
       +    echo "alice pays bob"
       +    invoice=$($bob addinvoice 0.05 "test")
       +    $alice lnpay $invoice --timeout=1 || true
       +    settled=$($alice list_channels | jq '.[] | .local_htlcs | .settles | length')
       +    if [[ "$settled" != "0" ]]; then
       +        echo "SETTLE_DELAY did not work, $settled != 0"
       +        exit 1
       +    fi
       +    ctx=$($alice get_channel_ctx $channel | jq '.hex' | tr -d '"')
       +    cp /tmp/alice/regtest/wallets/default_wallet /tmp/alice/regtest/wallets/toxic_wallet
       +    sleep 5
       +    settled=$($alice list_channels | jq '.[] | .local_htlcs | .settles | length')
       +    if [[ "$settled" != "1" ]]; then
       +        echo "SETTLE_DELAY did not work, $settled != 1"
       +        exit 1
       +    fi
       +    echo $($bob getbalance)
       +    echo "bob goes offline"
       +    $bob daemon stop
       +    ctx_id=$($bitcoin_cli sendrawtransaction $ctx)
       +    echo "alice breaches with old ctx:" $ctx_id
       +    new_blocks 1
       +    if [[ $($bitcoin_cli gettxout $ctx_id 0 | jq '.confirmations') != "1" ]]; then
       +        echo "breach tx not confirmed"
       +        exit 1
       +    fi
       +    echo "wait for cltv_expiry blocks"
       +    # note: this will let alice redeem to_local
       +    # because cltv_delay is the same as csv_delay
       +    new_blocks 144
       +    echo "alice spends to_local and htlc outputs"
       +    $alice daemon stop
       +    cp /tmp/alice/regtest/wallets/toxic_wallet /tmp/alice/regtest/wallets/default_wallet
       +    $alice daemon -s 127.0.0.1:51001:t start
       +    $alice daemon load_wallet
       +    # wait until alice has spent both ctx outputs
       +    while [[ $($bitcoin_cli gettxout $ctx_id 0) ]]; do
       +        echo "waiting until alice spends ctx outputs"
       +        sleep 1
       +    done
       +    while [[ $($bitcoin_cli gettxout $ctx_id 1) ]]; do
       +        echo "waiting until alice spends ctx outputs"
       +        sleep 1
       +    done
       +    new_blocks 1
       +    echo "bob comes back"
       +    $bob daemon -s 127.0.0.1:51001:t start
       +    $bob daemon load_wallet
       +    while [[ $($bitcoin_cli getmempoolinfo | jq '.size') != "1" ]]; do
       +        echo "waiting for bob's transaction"
       +        sleep 1
       +    done
       +    echo "mempool has 1 tx"
       +    new_blocks 1
       +    sleep 5
       +    balance=$($bob getbalance | jq '.confirmed')
       +    if (( $(echo "$balance < 0.049" | bc -l) )); then
       +        echo "htlc not redeemed."
       +        exit 1
       +    fi
       +    echo "bob balance $balance"
       +fi
   DIR diff --git a/electrum/tests/test_regtest.py b/electrum/tests/test_regtest.py
       t@@ -33,5 +33,8 @@ class TestLightning(unittest.TestCase):
            def test_redeem_htlcs(self):
                self.run_shell(['redeem_htlcs'])
        
       -    def test_breach_with_htlc(self):
       -        self.run_shell(['breach_with_htlc'])
       +    def test_breach_with_unspent_htlc(self):
       +        self.run_shell(['breach_with_unspent_htlc'])
       +
       +    def test_breach_with_spent_htlc(self):
       +        self.run_shell(['breach_with_spent_htlc'])