URI: 
       tlnchannel: store pre-signed sweep transactions after each new commitment - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
   DIR commit fde9f91902bcfa05bbc431f62f7d0bd7af3c92d2
   DIR parent 3019aa35cf3b6deca9ec42d30af782d8a74a9389
  HTML Author: ThomasV <thomasv@electrum.org>
       Date:   Sat, 15 Dec 2018 11:38:46 +0100
       
       lnchannel: store pre-signed sweep transactions after each new commitment
       
       Diffstat:
         M electrum/lnbase.py                  |      10 ++++++----
         M electrum/lnchan.py                  |      27 +++++++++++++++++++--------
         M electrum/lnsweep.py                 |      20 ++++++++++----------
         M electrum/lnworker.py                |      42 +++++++++++++------------------
         M electrum/transaction.py             |       3 +++
       
       5 files changed, 55 insertions(+), 47 deletions(-)
       ---
   DIR diff --git a/electrum/lnbase.py b/electrum/lnbase.py
       t@@ -499,9 +499,10 @@ class Peer(PrintError):
                        "constraints": ChannelConstraints(capacity=funding_sat, is_initiator=True, funding_txn_minimum_depth=funding_txn_minimum_depth, feerate=feerate),
                        "remote_commitment_to_be_revoked": None,
                }
       -        chan = Channel(chan_dict, payment_completed=self.lnworker.payment_completed)
       +        chan = Channel(chan_dict,
       +                       sweep_address=self.lnworker.sweep_address,
       +                       payment_completed=self.lnworker.payment_completed)
                chan.lnwatcher = self.lnwatcher
       -        chan.sweep_address = self.lnworker.sweep_address
                chan.get_preimage_and_invoice = self.lnworker.get_invoice  # FIXME hack.
                sig_64, _ = chan.sign_next_commitment()
                self.send_message("funding_created",
       t@@ -590,9 +591,10 @@ class Peer(PrintError):
                        "constraints": ChannelConstraints(capacity=funding_sat, is_initiator=False, funding_txn_minimum_depth=min_depth, feerate=feerate),
                        "remote_commitment_to_be_revoked": None,
                }
       -        chan = Channel(chan_dict, payment_completed=self.lnworker.payment_completed)
       +        chan = Channel(chan_dict,
       +                       sweep_adddress=self.lnworker.sweep_address,
       +                       payment_completed=self.lnworker.payment_completed)
                chan.lnwatcher = self.lnwatcher
       -        chan.sweep_address = self.lnworker.sweep_address
                chan.get_preimage_and_invoice = self.lnworker.get_invoice  # FIXME hack.
                remote_sig = funding_created['signature']
                chan.receive_new_commitment(remote_sig, [])
   DIR diff --git a/electrum/lnchan.py b/electrum/lnchan.py
       t@@ -44,6 +44,7 @@ from .lnutil import funding_output_script, LOCAL, REMOTE, HTLCOwner, make_closin
        from .lnutil import ScriptHtlc, PaymentFailure, calc_onchain_fees, RemoteMisbehaving, make_htlc_output_witness_script
        from .transaction import Transaction
        from .lnsweep import create_sweeptxs_for_their_just_revoked_ctx
       +from .lnsweep import create_sweeptxs_for_our_latest_ctx, create_sweeptxs_for_their_latest_ctx
        
        
        class ChannelJsonEncoder(json.JSONEncoder):
       t@@ -146,10 +147,11 @@ class Channel(PrintError):
                except:
                    return super().diagnostic_name()
        
       -    def __init__(self, state, name = None, payment_completed : Optional[Callable[[HTLCOwner, UpdateAddHtlc, bytes], None]] = None):
       +    def __init__(self, state, sweep_address = None, name = None, payment_completed : Optional[Callable[[HTLCOwner, UpdateAddHtlc, bytes], None]] = None):
                self.preimages = {}
                if not payment_completed:
                    payment_completed = lambda this, x, y, z: None
       +        self.sweep_address = sweep_address
                self.payment_completed = payment_completed
                assert 'local_state' not in state
                self.config = {}
       t@@ -203,9 +205,18 @@ class Channel(PrintError):
                for sub in (LOCAL, REMOTE):
                    self.log[sub].locked_in.update(self.log[sub].adds.keys())
        
       -        # used in lnworker.on_channel_closed
       -        self.local_commitment = self.current_commitment(LOCAL)
       -        self.remote_commitment = self.current_commitment(REMOTE)
       +        self.set_local_commitment(self.current_commitment(LOCAL))
       +        self.set_remote_commitment(self.current_commitment(REMOTE))
       +
       +    def set_local_commitment(self, ctx):
       +        self.local_commitment = ctx
       +        if self.sweep_address is not None:
       +            self.local_sweeptxs = create_sweeptxs_for_our_latest_ctx(self, self.local_commitment, self.sweep_address)
       +
       +    def set_remote_commitment(self, ctx):
       +        self.remote_commitment = ctx
       +        if self.sweep_address is not None:
       +            self.remote_sweeptxs = create_sweeptxs_for_their_latest_ctx(self, self.remote_commitment, self.sweep_address)
        
            def set_state(self, state: str):
                if self._state == 'FORCE_CLOSING':
       t@@ -389,7 +400,7 @@ class Channel(PrintError):
                    if self.constraints.is_initiator and self.pending_fee[FUNDEE_ACKED]:
                        self.pending_fee[FUNDER_SIGNED] = True
        
       -        self.local_commitment = self.pending_commitment(LOCAL)
       +        self.set_local_commitment(self.pending_commitment(LOCAL))
        
            def verify_htlc(self, htlc: UpdateAddHtlc, htlc_sigs: Sequence[bytes], we_receive: bool) -> int:
                _, this_point, _ = self.points()
       t@@ -438,7 +449,7 @@ class Channel(PrintError):
                    feerate=new_feerate
                )
        
       -        self.local_commitment = self.pending_commitment(LOCAL)
       +        self.set_local_commitment(self.pending_commitment(LOCAL))
        
                return RevokeAndAck(last_secret, next_point), "current htlcs"
        
       t@@ -530,8 +541,8 @@ class Channel(PrintError):
                    if self.constraints.is_initiator:
                        self.pending_fee[FUNDEE_ACKED] = True
        
       -        self.local_commitment = self.pending_commitment(LOCAL)
       -        self.remote_commitment = self.pending_commitment(REMOTE)
       +        self.set_local_commitment(self.pending_commitment(LOCAL))
       +        self.set_remote_commitment(self.pending_commitment(REMOTE))
                self.remote_commitment_to_be_revoked = prev_remote_commitment
                return received_this_batch, sent_this_batch
        
   DIR diff --git a/electrum/lnsweep.py b/electrum/lnsweep.py
       t@@ -143,7 +143,7 @@ def create_sweeptxs_for_our_latest_ctx(chan: 'Channel', ctx: Transaction,
            to_self_delay = chan.config[REMOTE].to_self_delay
            this_htlc_privkey = derive_privkey(secret=int.from_bytes(this_conf.htlc_basepoint.privkey, 'big'),
                                               per_commitment_point=our_pcp).to_bytes(32, 'big')
       -    txs = []
       +    txs = {}
            # to_local
            sweep_tx = maybe_create_sweeptx_that_spends_to_local_in_our_ctx(ctx=ctx,
                                                                            sweep_address=sweep_address,
       t@@ -151,7 +151,7 @@ def create_sweeptxs_for_our_latest_ctx(chan: 'Channel', ctx: Transaction,
                                                                            remote_revocation_pubkey=other_revocation_pubkey,
                                                                            to_self_delay=to_self_delay)
            if sweep_tx:
       -        txs.append(EncumberedTransaction('our_ctx_to_local', sweep_tx, csv_delay=to_self_delay, cltv_expiry=0))
       +        txs[sweep_tx.prevout(0)] = EncumberedTransaction('our_ctx_to_local', sweep_tx, csv_delay=to_self_delay, cltv_expiry=0)
            # HTLCs
            def create_txns_for_htlc(htlc: 'UpdateAddHtlc', is_received_htlc: bool) -> Tuple[Optional[Transaction], Optional[Transaction]]:
                if is_received_htlc:
       t@@ -184,16 +184,16 @@ def create_sweeptxs_for_our_latest_ctx(chan: 'Channel', ctx: Transaction,
            for htlc in offered_htlcs:
                htlc_tx, to_wallet_tx = create_txns_for_htlc(htlc, is_received_htlc=False)
                if htlc_tx and to_wallet_tx:
       -            txs.append(EncumberedTransaction(f'second_stage_to_wallet_{bh2u(htlc.payment_hash)}', to_wallet_tx, csv_delay=to_self_delay, cltv_expiry=0))
       -            txs.append(EncumberedTransaction(f'our_ctx_htlc_tx_{bh2u(htlc.payment_hash)}', htlc_tx, csv_delay=0, cltv_expiry=htlc.cltv_expiry))
       +            txs[to_wallet_tx.prevout(0)] = EncumberedTransaction(f'second_stage_to_wallet_{bh2u(htlc.payment_hash)}', to_wallet_tx, csv_delay=to_self_delay, cltv_expiry=0)
       +            txs[htlc_tx.prevout(0)] = EncumberedTransaction(f'our_ctx_htlc_tx_{bh2u(htlc.payment_hash)}', htlc_tx, csv_delay=0, cltv_expiry=htlc.cltv_expiry)
            # received HTLCs, in our ctx --> "success"
            # TODO consider carefully if "included_htlcs" is what we need here
            received_htlcs = list(chan.included_htlcs(LOCAL, REMOTE))  # type: List[UpdateAddHtlc]
            for htlc in received_htlcs:
                htlc_tx, to_wallet_tx = create_txns_for_htlc(htlc, is_received_htlc=True)
                if htlc_tx and to_wallet_tx:
       -            txs.append(EncumberedTransaction(f'second_stage_to_wallet_{bh2u(htlc.payment_hash)}', to_wallet_tx, csv_delay=to_self_delay, cltv_expiry=0))
       -            txs.append(EncumberedTransaction(f'our_ctx_htlc_tx_{bh2u(htlc.payment_hash)}', htlc_tx, csv_delay=0, cltv_expiry=0))
       +            txs[to_wallet_tx.prevout(0)] = EncumberedTransaction(f'second_stage_to_wallet_{bh2u(htlc.payment_hash)}', to_wallet_tx, csv_delay=to_self_delay, cltv_expiry=0)
       +            txs[htlc_tx.prevout(0)] = EncumberedTransaction(f'our_ctx_htlc_tx_{bh2u(htlc.payment_hash)}', htlc_tx, csv_delay=0, cltv_expiry=0)
            return txs
        
        
       t@@ -232,7 +232,7 @@ def create_sweeptxs_for_their_latest_ctx(chan: 'Channel', ctx: Transaction,
            other_payment_privkey = derive_privkey(other_payment_bp_privkey.secret_scalar, their_pcp)
            other_payment_privkey = ecc.ECPrivkey.from_secret_scalar(other_payment_privkey)
        
       -    txs = []
       +    txs = {}
            if per_commitment_secret:  # breach
                # to_local
                other_revocation_privkey = derive_blinded_privkey(other_conf.revocation_basepoint.privkey,
       t@@ -250,7 +250,7 @@ def create_sweeptxs_for_their_latest_ctx(chan: 'Channel', ctx: Transaction,
                                                                    sweep_address=sweep_address,
                                                                    our_payment_privkey=other_payment_privkey)
            if sweep_tx:
       -        txs.append(EncumberedTransaction('their_ctx_to_remote', sweep_tx, csv_delay=0, cltv_expiry=0))
       +        txs[sweep_tx.prevout(0)] = EncumberedTransaction('their_ctx_to_remote', sweep_tx, csv_delay=0, cltv_expiry=0)
            # 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
       t@@ -286,13 +286,13 @@ def create_sweeptxs_for_their_latest_ctx(chan: 'Channel', ctx: Transaction,
            for htlc in received_htlcs:
                sweep_tx = create_sweeptx_for_htlc(htlc, is_received_htlc=True)
                if sweep_tx:
       -            txs.append(EncumberedTransaction(f'their_ctx_sweep_htlc_{bh2u(htlc.payment_hash)}', sweep_tx, csv_delay=0, cltv_expiry=htlc.cltv_expiry))
       +            txs[prevout] = EncumberedTransaction(f'their_ctx_sweep_htlc_{bh2u(htlc.payment_hash)}', sweep_tx, csv_delay=0, cltv_expiry=htlc.cltv_expiry)
            # offered HTLCs, in their ctx --> "success"
            offered_htlcs = chan.included_htlcs_in_their_latest_ctxs(REMOTE)[ctn]  # type: List[UpdateAddHtlc]
            for htlc in offered_htlcs:
                sweep_tx = create_sweeptx_for_htlc(htlc, is_received_htlc=False)
                if sweep_tx:
       -            txs.append(EncumberedTransaction(f'their_ctx_sweep_htlc_{bh2u(htlc.payment_hash)}', sweep_tx, csv_delay=0, cltv_expiry=0))
       +            txs[prevout] = EncumberedTransaction(f'their_ctx_sweep_htlc_{bh2u(htlc.payment_hash)}', sweep_tx, csv_delay=0, cltv_expiry=0)
            return txs
        
        
   DIR diff --git a/electrum/lnworker.py b/electrum/lnworker.py
       t@@ -39,7 +39,6 @@ from .lnutil import (Outpoint, calc_short_channel_id, LNPeerAddr,
        from .i18n import _
        from .lnrouter import RouteEdge, is_route_sane_to_use
        from .address_synchronizer import TX_HEIGHT_LOCAL
       -from .lnsweep import create_sweeptxs_for_our_latest_ctx, create_sweeptxs_for_their_latest_ctx
        
        if TYPE_CHECKING:
            from .network import Network
       t@@ -78,11 +77,9 @@ class LNWorker(PrintError):
                self.peers = {}  # type: Dict[bytes, Peer]  # pubkey -> Peer
                self.channels = {}  # type: Dict[bytes, Channel]
                for x in wallet.storage.get("channels", []):
       -            c = Channel(x, payment_completed=self.payment_completed)
       +            c = Channel(x, sweep_address=self.sweep_address, payment_completed=self.payment_completed)
                    self.channels[c.channel_id] = c
       -
                    c.lnwatcher = network.lnwatcher
       -            c.sweep_address = self.sweep_address
                self.invoices = wallet.storage.get('lightning_invoices', {})  # type: Dict[str, Tuple[str,str]]  # RHASH -> (preimage, invoice)
                for chan_id, chan in self.channels.items():
                    self.network.lnwatcher.watch_channel(chan.get_funding_address(), chan.funding_outpoint.to_str())
       t@@ -314,34 +311,28 @@ class LNWorker(PrintError):
                self.network.trigger_callback('channel', chan)
                # remove from channel_db
                self.channel_db.remove_channel(chan.short_channel_id)
       -        # sweep
       -        our_ctx = chan.local_commitment
       -        their_ctx = chan.remote_commitment
       -        if txid == our_ctx.txid():
       +        # detect who closed
       +        if txid == chan.local_commitment.txid():
                    self.print_error('we force closed', funding_outpoint)
       -            # we force closed
       -            encumbered_sweeptxs = create_sweeptxs_for_our_latest_ctx(chan, our_ctx, chan.sweep_address)
       -        elif txid == their_ctx.txid():
       +            encumbered_sweeptxs = chan.local_sweeptxs
       +        elif txid == chan.remote_commitment.txid():
                    self.print_error('they force closed', funding_outpoint)
       -            # they force closed
       -            encumbered_sweeptxs = create_sweeptxs_for_their_latest_ctx(chan, their_ctx, chan.sweep_address)
       +            encumbered_sweeptxs = chan.remote_sweeptxs
                else:
       -            # cooperative close or breach
                    self.print_error('not sure who closed', funding_outpoint)
       -            encumbered_sweeptxs = []
       -
       -        local_height = self.network.get_local_height()
       -        for e_tx in encumbered_sweeptxs:
       -            txin = e_tx.tx.inputs()[0]
       -            prev_txid = txin['prevout_hash']
       -            txin_outpoint = Transaction.get_outpoint_from_txin(txin)
       -            spender = spenders.get(txin_outpoint)
       +            return
       +        # sweep
       +        for prevout, spender in spenders.items():
       +            e_tx = encumbered_sweeptxs.get(prevout)
       +            if e_tx is None:
       +                continue
                    if spender is not None:
       -                self.print_error('prev_tx already spent', prev_txid)
       +                self.print_error('outpoint already spent', prevout)
                        continue
       -            num_conf = self.network.lnwatcher.get_tx_height(prev_txid).conf
       +            prev_txid, prev_index = prevout.split(':')
                    broadcast = True
                    if e_tx.cltv_expiry:
       +                local_height = self.network.get_local_height()
                        if local_height > e_tx.cltv_expiry:
                            self.print_error(e_tx.name, 'CLTV ({} > {}) fulfilled'.format(local_height, e_tx.cltv_expiry))
                        else:
       t@@ -349,13 +340,14 @@ class LNWorker(PrintError):
                                             .format(e_tx.name, local_height, e_tx.cltv_expiry, funding_outpoint[:8], prev_txid[:8]))
                            broadcast = False
                    if e_tx.csv_delay:
       +                num_conf = self.network.lnwatcher.get_tx_height(prev_txid).conf
                        if num_conf < e_tx.csv_delay:
                            self.print_error(e_tx.name, 'waiting for {}: CSV ({} >= {}), funding outpoint {} and tx {}'
                                             .format(e_tx.name, num_conf, e_tx.csv_delay, funding_outpoint[:8], prev_txid[:8]))
                            broadcast = False
                    if broadcast:
                        if not await self.network.lnwatcher.broadcast_or_log(funding_outpoint, e_tx):
       -                    self.print_error(e_tx.name, f'could not publish encumbered tx: {str(e_tx)}, prev_txid: {prev_txid}, local_height', local_height)
       +                    self.print_error(e_tx.name, f'could not publish encumbered tx: {str(sweep_tx)}, prev_txid: {prev_txid}')
        
        
            @log_exceptions
   DIR diff --git a/electrum/transaction.py b/electrum/transaction.py
       t@@ -933,6 +933,9 @@ class Transaction:
                prevout_n = txin['prevout_n']
                return prevout_hash + ':%d' % prevout_n
        
       +    def prevout(self, index):
       +        return self.get_outpoint_from_txin(self.inputs()[index])
       +
            @classmethod
            def serialize_input(self, txin, script):
                # Prev hash and index