URI: 
       tLet lnworker sweep HTLC outputs after breach, instead of lnwatcher - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
   DIR commit 8d99fe8243d19a85962eff159562d7e7ee17085b
   DIR parent 3dacc525e63570b835246ee06e1c97a08c81deee
  HTML Author: ThomasV <thomasv@electrum.org>
       Date:   Thu, 30 May 2019 10:11:15 +0200
       
       Let lnworker sweep HTLC outputs after breach, instead of lnwatcher
       
       Diffstat:
         M electrum/lnchannel.py               |      15 ---------------
         M electrum/lnsweep.py                 |      90 ++++++++++---------------------
       
       2 files changed, 28 insertions(+), 77 deletions(-)
       ---
   DIR diff --git a/electrum/lnchannel.py b/electrum/lnchannel.py
       t@@ -806,18 +806,3 @@ class Channel(Logger):
                tx.add_signature_to_txin(0, none_idx, bh2u(remote_sig))
                assert tx.is_complete()
                return tx
       -
       -    def included_htlcs_in_their_latest_ctxs(self, htlc_initiator) -> Dict[int, List[UpdateAddHtlc]]:
       -        """ A map from commitment number to list of HTLCs in
       -            their latest two commitment transactions.
       -            The oldest might have been revoked.  """
       -        assert type(htlc_initiator) is HTLCOwner
       -        direction = RECEIVED if htlc_initiator == LOCAL else SENT
       -        old_ctn = self.config[REMOTE].ctn
       -        old_htlcs  = self.included_htlcs(REMOTE, direction, ctn=old_ctn)
       -
       -        new_ctn = self.config[REMOTE].ctn+1
       -        new_htlcs  = self.included_htlcs(REMOTE, direction, ctn=new_ctn)
       -
       -        return {old_ctn: old_htlcs,
       -                new_ctn: new_htlcs, }
   DIR diff --git a/electrum/lnsweep.py b/electrum/lnsweep.py
       t@@ -66,18 +66,7 @@ def create_sweeptxs_for_their_revoked_ctx(chan: 'Channel', ctx: Transaction, per
                                                                                 we_receive=not is_received_htlc,
                                                                                 commit=ctx,
                                                                                 htlc=htlc)
       -        htlc_tx_txin = htlc_tx.inputs()[0]
       -        htlc_output_witness_script = bfh(Transaction.get_preimage_script(htlc_tx_txin))
       -        # sweep directly from ctx
       -        direct_sweep_tx = maybe_create_sweeptx_for_their_ctx_htlc(
       -            ctx=ctx,
       -            sweep_address=sweep_address,
       -            htlc_output_witness_script=htlc_output_witness_script,
       -            privkey=other_revocation_privkey,
       -            preimage=None,
       -            is_revocation=True)
       -        # sweep from htlc tx
       -        secondstage_sweep_tx = create_sweeptx_that_spends_htlctx_that_spends_htlc_in_ctx(
       +        return create_sweeptx_that_spends_htlctx_that_spends_htlc_in_ctx(
                    'sweep_from_their_ctx_htlc_',
                    to_self_delay=0,
                    htlc_tx=htlc_tx,
       t@@ -85,23 +74,18 @@ def create_sweeptxs_for_their_revoked_ctx(chan: 'Channel', ctx: Transaction, per
                    sweep_address=sweep_address,
                    privkey=other_revocation_privkey,
                    is_revocation=True)
       -        return direct_sweep_tx, secondstage_sweep_tx, htlc_tx
            ctn = extract_ctn_from_tx_and_chan(ctx, chan)
            assert ctn == chan.config[REMOTE].ctn
            # received HTLCs, in their ctx
            received_htlcs = chan.included_htlcs(REMOTE, RECEIVED, ctn)
            for htlc in received_htlcs:
       -        direct_sweep_tx, secondstage_sweep_tx, htlc_tx = create_sweeptx_for_htlc(htlc, is_received_htlc=True)
       -        if direct_sweep_tx:
       -            txs.append(direct_sweep_tx)
       +        secondstage_sweep_tx = create_sweeptx_for_htlc(htlc, is_received_htlc=True)
                if secondstage_sweep_tx:
                    txs.append(secondstage_sweep_tx)
            # offered HTLCs, in their ctx
            offered_htlcs = chan.included_htlcs(REMOTE, SENT, ctn)
            for htlc in offered_htlcs:
       -        direct_sweep_tx, secondstage_sweep_tx, htlc_tx = create_sweeptx_for_htlc(htlc, is_received_htlc=False)
       -        if direct_sweep_tx:
       -            txs.append(direct_sweep_tx)
       +        secondstage_sweep_tx = create_sweeptx_for_htlc(htlc, is_received_htlc=False)
                if secondstage_sweep_tx:
                    txs.append(secondstage_sweep_tx)
            return txs
       t@@ -262,10 +246,8 @@ def create_sweeptxs_for_our_ctx(chan: 'Channel', ctx: Transaction,
        def create_sweeptxs_for_their_ctx(chan: 'Channel', ctx: Transaction,
                                          sweep_address: str) -> Dict[str,Transaction]:
            """Handle the case when the remote force-closes with their ctx.
       -    Regardless of it is a breach or not, construct sweep tx for 'to_remote'.
       -    If it is a breach, also construct sweep tx for 'to_local'.
       -    Sweep txns for HTLCs are only constructed if it is NOT a breach, as
       -    lnchannel does not store old HTLCs.
       +    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.
            """
            this_conf, other_conf = get_ordered_channel_configs(chan=chan, for_us=False)
            ctn = extract_ctn_from_tx_and_chan(ctx, chan)
       t@@ -274,20 +256,23 @@ def create_sweeptxs_for_their_ctx(chan: 'Channel', ctx: Transaction,
            per_commitment_secret = None
            if ctn == this_conf.ctn:
                their_pcp = this_conf.current_per_commitment_point
       +        is_revocation = False
            elif ctn == this_conf.ctn + 1:
                their_pcp = this_conf.next_per_commitment_point
       +        is_revocation = False
            elif ctn < this_conf.ctn:  # breach
                try:
                    per_commitment_secret = this_conf.revocation_store.retrieve_secret(RevocationStore.START_INDEX - ctn)
                except UnableToDeriveSecret:
                    return {}
                their_pcp = ecc.ECPrivkey(per_commitment_secret).get_public_key_bytes(compressed=True)
       +        is_revocation = True
            else:
                return {}
            # prep
            other_revocation_pubkey = derive_blinded_pubkey(other_conf.revocation_basepoint.pubkey, their_pcp)
       -    other_htlc_privkey = derive_privkey(secret=int.from_bytes(other_conf.htlc_basepoint.privkey, 'big'),
       -                                        per_commitment_point=their_pcp)
       +    other_revocation_privkey = derive_blinded_privkey(other_conf.revocation_basepoint.privkey, per_commitment_secret)
       +    other_htlc_privkey = derive_privkey(secret=int.from_bytes(other_conf.htlc_basepoint.privkey, 'big'), per_commitment_point=their_pcp)
            other_htlc_privkey = ecc.ECPrivkey.from_secret_scalar(other_htlc_privkey)
            this_htlc_pubkey = derive_pubkey(this_conf.htlc_basepoint.pubkey, their_pcp)
            other_payment_bp_privkey = ecc.ECPrivkey(other_conf.payment_basepoint.privkey)
       t@@ -308,13 +293,8 @@ def create_sweeptxs_for_their_ctx(chan: 'Channel', ctx: Transaction,
                    our_payment_privkey=other_payment_privkey)
                txs[sweep_tx.prevout(0)] = sweep_tx
            # HTLCs
       -    # from their ctx, we can only redeem HTLCs if the ctx was not revoked,
       -    # as old HTLCs are not stored. (if it was revoked, then we should have presigned txns
       -    # to handle the breach already; out of scope here)
       -    if ctn not in (this_conf.ctn, this_conf.ctn + 1):
       -        return txs
            def create_sweeptx_for_htlc(htlc: 'UpdateAddHtlc', is_received_htlc: bool) -> Optional[Transaction]:
       -        if not is_received_htlc:
       +        if not is_received_htlc and not is_revocation:
                    try:
                        preimage = chan.lnworker.get_preimage(htlc.payment_hash)
                    except UnknownPaymentHash as e:
       t@@ -329,23 +309,29 @@ def create_sweeptxs_for_their_ctx(chan: 'Channel', ctx: Transaction,
                    local_htlc_pubkey=this_htlc_pubkey,
                    payment_hash=htlc.payment_hash,
                    cltv_expiry=htlc.cltv_expiry)
       -        sweep_tx = maybe_create_sweeptx_for_their_ctx_htlc(
       -            ctx=ctx,
       -            sweep_address=sweep_address,
       -            htlc_output_witness_script=htlc_output_witness_script,
       -            privkey=other_htlc_privkey.get_secret_bytes(),
       -            preimage=preimage,
       -            is_revocation=False,
       -            cltv_expiry=htlc.cltv_expiry if is_received_htlc else 0)
       -        return sweep_tx
       +        htlc_address = redeem_script_to_address('p2wsh', bh2u(htlc_output_witness_script))
       +        # FIXME handle htlc_address collision
       +        # also: https://github.com/lightningnetwork/lightning-rfc/issues/448
       +        output_idx = ctx.get_output_idx_from_address(htlc_address)
       +        if output_idx is not None:
       +            sweep_tx = create_sweeptx_their_ctx_htlc(
       +                ctx=ctx,
       +                witness_script=htlc_output_witness_script,
       +                sweep_address=sweep_address,
       +                preimage=preimage,
       +                output_idx=output_idx,
       +                privkey=other_revocation_privkey if is_revocation else other_htlc_privkey.get_secret_bytes(),
       +                is_revocation=is_revocation,
       +                cltv_expiry=htlc.cltv_expiry if is_received_htlc and not is_revocation else 0)
       +            return sweep_tx
            # received HTLCs, in their ctx --> "timeout"
       -    received_htlcs = chan.included_htlcs_in_their_latest_ctxs(LOCAL)[ctn]  # type: List[UpdateAddHtlc]
       +    received_htlcs = chan.included_htlcs(REMOTE, RECEIVED, ctn=ctn)  # type: List[UpdateAddHtlc]
            for htlc in received_htlcs:
                sweep_tx = create_sweeptx_for_htlc(htlc, is_received_htlc=True)
                if sweep_tx:
                    txs[sweep_tx.prevout(0)] = sweep_tx
            # offered HTLCs, in their ctx --> "success"
       -    offered_htlcs = chan.included_htlcs_in_their_latest_ctxs(REMOTE)[ctn]  # type: List[UpdateAddHtlc]
       +    offered_htlcs = chan.included_htlcs(REMOTE, SENT, ctn=ctn)  # type: List[UpdateAddHtlc]
            for htlc in offered_htlcs:
                sweep_tx = create_sweeptx_for_htlc(htlc, is_received_htlc=False)
                if sweep_tx:
       t@@ -375,26 +361,6 @@ def create_htlctx_that_spends_from_our_ctx(chan: 'Channel', our_pcp: bytes,
            return witness_script, htlc_tx
        
        
       -def maybe_create_sweeptx_for_their_ctx_htlc(ctx: Transaction, sweep_address: str,
       -                                            htlc_output_witness_script: bytes,
       -                                            privkey: bytes, is_revocation: bool,
       -                                            preimage: Optional[bytes], cltv_expiry: int = 0) -> Optional[Transaction]:
       -    htlc_address = redeem_script_to_address('p2wsh', bh2u(htlc_output_witness_script))
       -    # FIXME handle htlc_address collision
       -    # also: https://github.com/lightningnetwork/lightning-rfc/issues/448
       -    output_idx = ctx.get_output_idx_from_address(htlc_address)
       -    if output_idx is None: return None
       -    sweep_tx = create_sweeptx_their_ctx_htlc(ctx=ctx,
       -                                             witness_script=htlc_output_witness_script,
       -                                             sweep_address=sweep_address,
       -                                             preimage=preimage,
       -                                             output_idx=output_idx,
       -                                             privkey=privkey,
       -                                             is_revocation=is_revocation,
       -                                             cltv_expiry=cltv_expiry)
       -    return sweep_tx
       -
       -
        def create_sweeptx_their_ctx_htlc(ctx: Transaction, witness_script: bytes, sweep_address: str,
                                          preimage: Optional[bytes], output_idx: int,
                                          privkey: bytes, is_revocation: bool, cltv_expiry: int,