URI: 
       trename lnhtlc->lnchan, HTLCStateMachine->Channel - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
   DIR commit 1763d02b05fca7c27ef7892271110a6853821e5e
   DIR parent b26dc66567fe40f5623d274a47b131893e368ee9
  HTML Author: Janus <ysangkok@gmail.com>
       Date:   Thu, 11 Oct 2018 17:15:25 +0200
       
       rename lnhtlc->lnchan, HTLCStateMachine->Channel
       
       Diffstat:
         M electrum/gui/qt/channels_list.py    |       6 +++---
         M electrum/lnbase.py                  |       6 +++---
         A electrum/lnchan.py                  |     810 +++++++++++++++++++++++++++++++
         D electrum/lnhtlc.py                  |     810 -------------------------------
         M electrum/lnworker.py                |       6 +++---
         A electrum/tests/test_lnchan.py       |     367 ++++++++++++++++++++++++++++++
         D electrum/tests/test_lnhtlc.py       |     367 ------------------------------
         M electrum/tests/test_lnutil.py       |       4 ++--
       
       8 files changed, 1188 insertions(+), 1188 deletions(-)
       ---
   DIR diff --git a/electrum/gui/qt/channels_list.py b/electrum/gui/qt/channels_list.py
       t@@ -5,7 +5,7 @@ from PyQt5.QtWidgets import *
        
        from electrum.util import inv_dict, bh2u, bfh
        from electrum.i18n import _
       -from electrum.lnhtlc import HTLCStateMachine
       +from electrum.lnchan import Channel
        from electrum.lnutil import LOCAL, REMOTE, ConnStringFormatError
        
        from .util import MyTreeWidget, SortableTreeWidgetItem, WindowModalDialog, Buttons, OkButton, CancelButton
       t@@ -13,7 +13,7 @@ from .amountedit import BTCAmountEdit
        
        class ChannelsList(MyTreeWidget):
            update_rows = QtCore.pyqtSignal()
       -    update_single_row = QtCore.pyqtSignal(HTLCStateMachine)
       +    update_single_row = QtCore.pyqtSignal(Channel)
        
            def __init__(self, parent):
                MyTreeWidget.__init__(self, parent, self.create_menu, [_('Node ID'), _('Balance'), _('Remote'), _('Status')], 0)
       t@@ -43,7 +43,7 @@ class ChannelsList(MyTreeWidget):
                menu.addAction(_("Force-close channel"), close)
                menu.exec_(self.viewport().mapToGlobal(position))
        
       -    @QtCore.pyqtSlot(HTLCStateMachine)
       +    @QtCore.pyqtSlot(Channel)
            def do_update_single_row(self, chan):
                for i in range(self.topLevelItemCount()):
                    item = self.topLevelItem(i)
   DIR diff --git a/electrum/lnbase.py b/electrum/lnbase.py
       t@@ -27,7 +27,7 @@ from .util import PrintError, bh2u, print_error, bfh, log_exceptions
        from .transaction import Transaction, TxOutput
        from .lnonion import new_onion_packet, OnionHopsDataSingle, OnionPerHop, decode_onion_error, OnionFailureCode
        from .lnaddr import lndecode
       -from .lnhtlc import HTLCStateMachine, RevokeAndAck, htlcsum
       +from .lnchan import Channel, RevokeAndAck, htlcsum
        from .lnutil import (Outpoint, LocalConfig, ChannelConfig,
                             RemoteConfig, OnlyPubkeyKeypair, ChannelConstraints, RevocationStore,
                             funding_output_script, get_ecdh, get_per_commitment_secret_from_seed,
       t@@ -641,7 +641,7 @@ 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,
                }
       -        m = HTLCStateMachine(chan)
       +        m = Channel(chan)
                m.lnwatcher = self.lnwatcher
                m.sweep_address = self.lnworker.sweep_address
                sig_64, _ = m.sign_next_commitment()
       t@@ -737,7 +737,7 @@ 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,
                }
       -        m = HTLCStateMachine(chan)
       +        m = Channel(chan)
                m.lnwatcher = self.lnwatcher
                m.sweep_address = self.lnworker.sweep_address
                remote_sig = funding_created['signature']
   DIR diff --git a/electrum/lnchan.py b/electrum/lnchan.py
       t@@ -0,0 +1,810 @@
       +# ported from lnd 42de4400bff5105352d0552155f73589166d162b
       +from collections import namedtuple, defaultdict
       +import binascii
       +import json
       +from enum import Enum, auto
       +from typing import Optional
       +
       +from .util import bfh, PrintError, bh2u
       +from .bitcoin import Hash, TYPE_SCRIPT, TYPE_ADDRESS
       +from .bitcoin import redeem_script_to_address
       +from .crypto import sha256
       +from . import ecc
       +from .lnutil import Outpoint, LocalConfig, RemoteConfig, Keypair, OnlyPubkeyKeypair, ChannelConstraints, RevocationStore, EncumberedTransaction
       +from .lnutil import get_per_commitment_secret_from_seed
       +from .lnutil import make_commitment_output_to_remote_address, make_commitment_output_to_local_witness_script
       +from .lnutil import secret_to_pubkey, derive_privkey, derive_pubkey, derive_blinded_pubkey, derive_blinded_privkey
       +from .lnutil import sign_and_get_sig_string
       +from .lnutil import make_htlc_tx_with_open_channel, make_commitment, make_received_htlc, make_offered_htlc
       +from .lnutil import HTLC_TIMEOUT_WEIGHT, HTLC_SUCCESS_WEIGHT
       +from .lnutil import funding_output_script, LOCAL, REMOTE, HTLCOwner, make_closing_tx, make_outputs
       +from .lnutil import ScriptHtlc, SENT, RECEIVED
       +from .transaction import Transaction, TxOutput, construct_witness
       +from .simple_config import SimpleConfig, FEERATE_FALLBACK_STATIC_FEE
       +
       +
       +RevokeAndAck = namedtuple("RevokeAndAck", ["per_commitment_secret", "next_per_commitment_point"])
       +
       +class FeeUpdateProgress(Enum):
       +    FUNDEE_SIGNED = auto()
       +    FUNDEE_ACKED =  auto()
       +    FUNDER_SIGNED = auto()
       +
       +FUNDEE_SIGNED = FeeUpdateProgress.FUNDEE_SIGNED
       +FUNDEE_ACKED = FeeUpdateProgress.FUNDEE_ACKED
       +FUNDER_SIGNED = FeeUpdateProgress.FUNDER_SIGNED
       +
       +class FeeUpdate(defaultdict):
       +    def __init__(self, chan, rate):
       +        super().__init__(lambda: False)
       +        self.rate = rate
       +        self.chan = chan
       +
       +    def pending_feerate(self, subject):
       +        if self[FUNDEE_ACKED]:
       +            return self.rate
       +        if subject == REMOTE and self.chan.constraints.is_initiator:
       +            return self.rate
       +        if subject == LOCAL and not self.chan.constraints.is_initiator:
       +            return self.rate
       +        # implicit return None
       +
       +class UpdateAddHtlc(namedtuple('UpdateAddHtlc', ['amount_msat', 'payment_hash', 'cltv_expiry', 'locked_in', 'htlc_id'])):
       +    __slots__ = ()
       +    def __new__(cls, *args, **kwargs):
       +        if len(args) > 0:
       +            args = list(args)
       +            if type(args[1]) is str:
       +                args[1] = bfh(args[1])
       +            args[3] = {HTLCOwner(int(x)): y for x,y in args[3].items()}
       +            return super().__new__(cls, *args)
       +        if type(kwargs['payment_hash']) is str:
       +            kwargs['payment_hash'] = bfh(kwargs['payment_hash'])
       +        if 'locked_in' not in kwargs:
       +            kwargs['locked_in'] = {LOCAL: None, REMOTE: None}
       +        else:
       +            kwargs['locked_in'] = {HTLCOwner(int(x)): y for x,y in kwargs['locked_in'].items()}
       +        return super().__new__(cls, **kwargs)
       +
       +def decodeAll(d, local):
       +    for k, v in d.items():
       +        if k == 'revocation_store':
       +            yield (k, RevocationStore.from_json_obj(v))
       +        elif k.endswith("_basepoint") or k.endswith("_key"):
       +            if local:
       +                yield (k, Keypair(**dict(decodeAll(v, local))))
       +            else:
       +                yield (k, OnlyPubkeyKeypair(**dict(decodeAll(v, local))))
       +        elif k in ["node_id", "channel_id", "short_channel_id", "pubkey", "privkey", "current_per_commitment_point", "next_per_commitment_point", "per_commitment_secret_seed", "current_commitment_signature", "current_htlc_signatures"] and v is not None:
       +            yield (k, binascii.unhexlify(v))
       +        else:
       +            yield (k, v)
       +
       +def htlcsum(htlcs):
       +    return sum([x.amount_msat for x in htlcs])
       +
       +class Channel(PrintError):
       +    def diagnostic_name(self):
       +        return str(self.name)
       +
       +    def __init__(self, state, name = None):
       +        assert 'local_state' not in state
       +        self.config = {}
       +        self.config[LOCAL] = state["local_config"]
       +        if type(self.config[LOCAL]) is not LocalConfig:
       +            conf = dict(decodeAll(self.config[LOCAL], True))
       +            self.config[LOCAL] = LocalConfig(**conf)
       +        assert type(self.config[LOCAL].htlc_basepoint.privkey) is bytes
       +
       +        self.config[REMOTE] = state["remote_config"]
       +        if type(self.config[REMOTE]) is not RemoteConfig:
       +            conf = dict(decodeAll(self.config[REMOTE], False))
       +            self.config[REMOTE] = RemoteConfig(**conf)
       +        assert type(self.config[REMOTE].htlc_basepoint.pubkey) is bytes
       +
       +        self.channel_id = bfh(state["channel_id"]) if type(state["channel_id"]) not in (bytes, type(None)) else state["channel_id"]
       +        self.constraints = ChannelConstraints(**state["constraints"]) if type(state["constraints"]) is not ChannelConstraints else state["constraints"]
       +        self.funding_outpoint = Outpoint(**dict(decodeAll(state["funding_outpoint"], False))) if type(state["funding_outpoint"]) is not Outpoint else state["funding_outpoint"]
       +        self.node_id = bfh(state["node_id"]) if type(state["node_id"]) not in (bytes, type(None)) else state["node_id"]
       +        self.short_channel_id = bfh(state["short_channel_id"]) if type(state["short_channel_id"]) not in (bytes, type(None)) else state["short_channel_id"]
       +        self.short_channel_id_predicted = self.short_channel_id
       +        self.onion_keys = {int(k): bfh(v) for k,v in state['onion_keys'].items()} if 'onion_keys' in state else {}
       +
       +        # FIXME this is a tx serialised in the custom electrum partial tx format.
       +        # we should not persist txns in this format. we should persist htlcs, and be able to derive
       +        # any past commitment transaction and use that instead; until then...
       +        self.remote_commitment_to_be_revoked = Transaction(state["remote_commitment_to_be_revoked"])
       +
       +        template = lambda: {'adds': {}, 'settles': []}
       +        self.log = {LOCAL: template(), REMOTE: template()}
       +        for strname, subject in [('remote_log', REMOTE), ('local_log', LOCAL)]:
       +            if strname not in state: continue
       +            for y in state[strname]:
       +                htlc = UpdateAddHtlc(**y)
       +                self.log[subject]['adds'][htlc.htlc_id] = htlc
       +
       +        self.name = name
       +
       +        self.fee_mgr = []
       +
       +        self.local_commitment = self.pending_local_commitment
       +        self.remote_commitment = self.pending_remote_commitment
       +
       +        self._is_funding_txo_spent = None  # "don't know"
       +        self.set_state('DISCONNECTED')
       +
       +        self.lnwatcher = None
       +
       +        self.settled = {LOCAL: state.get('settled_local', []), REMOTE: state.get('settled_remote', [])}
       +
       +    def set_state(self, state: str):
       +        self._state = state
       +
       +    def get_state(self):
       +        return self._state
       +
       +    def set_funding_txo_spentness(self, is_spent: bool):
       +        assert isinstance(is_spent, bool)
       +        self._is_funding_txo_spent = is_spent
       +
       +    def should_try_to_reestablish_peer(self) -> bool:
       +        return self._is_funding_txo_spent is False and self._state == 'DISCONNECTED'
       +
       +    def get_funding_address(self):
       +        script = funding_output_script(self.config[LOCAL], self.config[REMOTE])
       +        return redeem_script_to_address('p2wsh', script)
       +
       +    def add_htlc(self, htlc):
       +        """
       +        AddHTLC adds an HTLC to the state machine's local update log. This method
       +        should be called when preparing to send an outgoing HTLC.
       +        """
       +        assert type(htlc) is dict
       +        htlc = UpdateAddHtlc(**htlc, htlc_id=self.config[LOCAL].next_htlc_id)
       +        self.log[LOCAL]['adds'][htlc.htlc_id] = htlc
       +        self.print_error("add_htlc")
       +        self.config[LOCAL]=self.config[LOCAL]._replace(next_htlc_id=htlc.htlc_id + 1)
       +        return htlc.htlc_id
       +
       +    def receive_htlc(self, htlc):
       +        """
       +        ReceiveHTLC adds an HTLC to the state machine's remote update log. This
       +        method should be called in response to receiving a new HTLC from the remote
       +        party.
       +        """
       +        assert type(htlc) is dict
       +        htlc = UpdateAddHtlc(**htlc, htlc_id = self.config[REMOTE].next_htlc_id)
       +        self.log[REMOTE]['adds'][htlc.htlc_id] = htlc
       +        self.print_error("receive_htlc")
       +        self.config[REMOTE]=self.config[REMOTE]._replace(next_htlc_id=htlc.htlc_id + 1)
       +        return htlc.htlc_id
       +
       +    def sign_next_commitment(self):
       +        """
       +        SignNextCommitment signs a new commitment which includes any previous
       +        unsettled HTLCs, any new HTLCs, and any modifications to prior HTLCs
       +        committed in previous commitment updates. Signing a new commitment
       +        decrements the available revocation window by 1. After a successful method
       +        call, the remote party's commitment chain is extended by a new commitment
       +        which includes all updates to the HTLC log prior to this method invocation.
       +        The first return parameter is the signature for the commitment transaction
       +        itself, while the second parameter is a slice of all HTLC signatures (if
       +        any). The HTLC signatures are sorted according to the BIP 69 order of the
       +        HTLC's on the commitment transaction.
       +        """
       +        for htlc in self.log[LOCAL]['adds'].values():
       +            if htlc.locked_in[LOCAL] is None:
       +                htlc.locked_in[LOCAL] = self.config[LOCAL].ctn
       +        self.print_error("sign_next_commitment")
       +
       +        pending_remote_commitment = self.pending_remote_commitment
       +        sig_64 = sign_and_get_sig_string(pending_remote_commitment, self.config[LOCAL], self.config[REMOTE])
       +
       +        their_remote_htlc_privkey_number = derive_privkey(
       +            int.from_bytes(self.config[LOCAL].htlc_basepoint.privkey, 'big'),
       +            self.config[REMOTE].next_per_commitment_point)
       +        their_remote_htlc_privkey = their_remote_htlc_privkey_number.to_bytes(32, 'big')
       +
       +        for_us = False
       +
       +        htlcsigs = []
       +        for we_receive, htlcs in zip([True, False], [self.included_htlcs(REMOTE, REMOTE), self.included_htlcs(REMOTE, LOCAL)]):
       +            for htlc in htlcs:
       +                args = [self.config[REMOTE].next_per_commitment_point, for_us, we_receive, pending_remote_commitment, htlc]
       +                htlc_tx = make_htlc_tx_with_open_channel(self, *args)
       +                sig = bfh(htlc_tx.sign_txin(0, their_remote_htlc_privkey))
       +                htlc_sig = ecc.sig_string_from_der_sig(sig[:-1])
       +                htlcsigs.append((pending_remote_commitment.htlc_output_indices[htlc.payment_hash], htlc_sig))
       +
       +        for pending_fee in self.fee_mgr:
       +            if not self.constraints.is_initiator:
       +                pending_fee[FUNDEE_SIGNED] = True
       +            if self.constraints.is_initiator and pending_fee[FUNDEE_ACKED]:
       +                pending_fee[FUNDER_SIGNED] = True
       +
       +        self.process_new_offchain_ctx(pending_remote_commitment, ours=False)
       +
       +        htlcsigs.sort()
       +        htlcsigs = [x[1] for x in htlcsigs]
       +
       +        return sig_64, htlcsigs
       +
       +    def receive_new_commitment(self, sig, htlc_sigs):
       +        """
       +        ReceiveNewCommitment process a signature for a new commitment state sent by
       +        the remote party. This method should be called in response to the
       +        remote party initiating a new change, or when the remote party sends a
       +        signature fully accepting a new state we've initiated. If we are able to
       +        successfully validate the signature, then the generated commitment is added
       +        to our local commitment chain. Once we send a revocation for our prior
       +        state, then this newly added commitment becomes our current accepted channel
       +        state.
       +        """
       +
       +        self.print_error("receive_new_commitment")
       +        for htlc in self.log[REMOTE]['adds'].values():
       +            if htlc.locked_in[REMOTE] is None:
       +                htlc.locked_in[REMOTE] = self.config[REMOTE].ctn
       +        assert len(htlc_sigs) == 0 or type(htlc_sigs[0]) is bytes
       +
       +        pending_local_commitment = self.pending_local_commitment
       +        preimage_hex = pending_local_commitment.serialize_preimage(0)
       +        pre_hash = Hash(bfh(preimage_hex))
       +        if not ecc.verify_signature(self.config[REMOTE].multisig_key.pubkey, sig, pre_hash):
       +            raise Exception('failed verifying signature of our updated commitment transaction: ' + bh2u(sig) + ' preimage is ' + preimage_hex)
       +
       +        _, this_point, _ = self.points
       +
       +        for htlcs, we_receive in [(self.included_htlcs(LOCAL, REMOTE), True), (self.included_htlcs(LOCAL, LOCAL), False)]:
       +            for htlc in htlcs:
       +                htlc_tx = make_htlc_tx_with_open_channel(self, this_point, True, we_receive, pending_local_commitment, htlc)
       +                pre_hash = Hash(bfh(htlc_tx.serialize_preimage(0)))
       +                remote_htlc_pubkey = derive_pubkey(self.config[REMOTE].htlc_basepoint.pubkey, this_point)
       +                for idx, sig in enumerate(htlc_sigs):
       +                    if ecc.verify_signature(remote_htlc_pubkey, sig, pre_hash):
       +                        del htlc_sigs[idx]
       +                        break
       +                else:
       +                    raise Exception(f'failed verifying HTLC signatures: {htlc}')
       +        if len(htlc_sigs) != 0: # all sigs should have been popped above
       +            raise Exception('failed verifying HTLC signatures: invalid amount of correct signatures')
       +
       +        for pending_fee in self.fee_mgr:
       +            if not self.constraints.is_initiator:
       +                pending_fee[FUNDEE_SIGNED] = True
       +            if self.constraints.is_initiator and pending_fee[FUNDEE_ACKED]:
       +                pending_fee[FUNDER_SIGNED] = True
       +
       +        self.process_new_offchain_ctx(pending_local_commitment, ours=True)
       +
       +
       +    def revoke_current_commitment(self):
       +        """
       +        RevokeCurrentCommitment revokes the next lowest unrevoked commitment
       +        transaction in the local commitment chain. As a result the edge of our
       +        revocation window is extended by one, and the tail of our local commitment
       +        chain is advanced by a single commitment. This now lowest unrevoked
       +        commitment becomes our currently accepted state within the channel. This
       +        method also returns the set of HTLC's currently active within the commitment
       +        transaction. This return value allows callers to act once an HTLC has been
       +        locked into our commitment transaction.
       +        """
       +        self.print_error("revoke_current_commitment")
       +
       +        last_secret, this_point, next_point = self.points
       +
       +        new_feerate = self.constraints.feerate
       +
       +        for pending_fee in self.fee_mgr[:]:
       +            if not self.constraints.is_initiator and pending_fee[FUNDEE_SIGNED]:
       +                new_feerate = pending_fee.rate
       +                self.fee_mgr.remove(pending_fee)
       +                print("FEERATE CHANGE COMPLETE (non-initiator)")
       +            if self.constraints.is_initiator and pending_fee[FUNDER_SIGNED]:
       +                new_feerate = pending_fee.rate
       +                self.fee_mgr.remove(pending_fee)
       +                print("FEERATE CHANGE COMPLETE (initiator)")
       +
       +        self.config[LOCAL]=self.config[LOCAL]._replace(
       +            ctn=self.config[LOCAL].ctn + 1,
       +        )
       +        self.constraints=self.constraints._replace(
       +            feerate=new_feerate
       +        )
       +
       +        self.local_commitment = self.pending_local_commitment
       +
       +        return RevokeAndAck(last_secret, next_point), "current htlcs"
       +
       +    @property
       +    def points(self):
       +        last_small_num = self.config[LOCAL].ctn
       +        this_small_num = last_small_num + 1
       +        next_small_num = last_small_num + 2
       +        last_secret = get_per_commitment_secret_from_seed(self.config[LOCAL].per_commitment_secret_seed, RevocationStore.START_INDEX - last_small_num)
       +        this_secret = get_per_commitment_secret_from_seed(self.config[LOCAL].per_commitment_secret_seed, RevocationStore.START_INDEX - this_small_num)
       +        this_point = secret_to_pubkey(int.from_bytes(this_secret, 'big'))
       +        next_secret = get_per_commitment_secret_from_seed(self.config[LOCAL].per_commitment_secret_seed, RevocationStore.START_INDEX - next_small_num)
       +        next_point = secret_to_pubkey(int.from_bytes(next_secret, 'big'))
       +        return last_secret, this_point, next_point
       +
       +    # TODO batch sweeps
       +    # TODO sweep HTLC outputs
       +    def process_new_offchain_ctx(self, ctx, ours: bool):
       +        if not self.lnwatcher:
       +            return
       +        outpoint = self.funding_outpoint.to_str()
       +        if ours:
       +            ctn = self.config[LOCAL].ctn + 1
       +            our_per_commitment_secret = get_per_commitment_secret_from_seed(
       +                self.config[LOCAL].per_commitment_secret_seed, RevocationStore.START_INDEX - ctn)
       +            our_cur_pcp = ecc.ECPrivkey(our_per_commitment_secret).get_public_key_bytes(compressed=True)
       +            encumbered_sweeptx = maybe_create_sweeptx_for_our_ctx_to_local(self, ctx, our_cur_pcp, self.sweep_address)
       +        else:
       +            their_cur_pcp = self.config[REMOTE].next_per_commitment_point
       +            encumbered_sweeptx = maybe_create_sweeptx_for_their_ctx_to_remote(self, ctx, their_cur_pcp, self.sweep_address)
       +        self.lnwatcher.add_sweep_tx(outpoint, ctx.txid(), encumbered_sweeptx)
       +
       +    def process_new_revocation_secret(self, per_commitment_secret: bytes):
       +        if not self.lnwatcher:
       +            return
       +        outpoint = self.funding_outpoint.to_str()
       +        ctx = self.remote_commitment_to_be_revoked
       +        encumbered_sweeptx = maybe_create_sweeptx_for_their_ctx_to_local(self, ctx, per_commitment_secret, self.sweep_address)
       +        self.lnwatcher.add_sweep_tx(outpoint, ctx.txid(), encumbered_sweeptx)
       +
       +    def receive_revocation(self, revocation):
       +        """
       +        ReceiveRevocation processes a revocation sent by the remote party for the
       +        lowest unrevoked commitment within their commitment chain. We receive a
       +        revocation either during the initial session negotiation wherein revocation
       +        windows are extended, or in response to a state update that we initiate. If
       +        successful, then the remote commitment chain is advanced by a single
       +        commitment, and a log compaction is attempted.
       +
       +        Returns the forwarding package corresponding to the remote commitment height
       +        that was revoked.
       +        """
       +        self.print_error("receive_revocation")
       +
       +        cur_point = self.config[REMOTE].current_per_commitment_point
       +        derived_point = ecc.ECPrivkey(revocation.per_commitment_secret).get_public_key_bytes(compressed=True)
       +        if cur_point != derived_point:
       +            raise Exception('revoked secret not for current point')
       +
       +        # FIXME not sure this is correct... but it seems to work
       +        # if there are update_add_htlc msgs between commitment_signed and rev_ack,
       +        # this might break
       +        prev_remote_commitment = self.pending_remote_commitment
       +
       +        self.config[REMOTE].revocation_store.add_next_entry(revocation.per_commitment_secret)
       +        self.process_new_revocation_secret(revocation.per_commitment_secret)
       +
       +        def mark_settled(subject):
       +            """
       +            find pending settlements for subject (LOCAL or REMOTE) and mark them settled, return value of settled htlcs
       +            """
       +            old_amount = htlcsum(self.htlcs(subject, False))
       +
       +            for htlc_id in self.log[-subject]['settles']:
       +                adds = self.log[subject]['adds']
       +                htlc = adds.pop(htlc_id)
       +                self.settled[subject].append(htlc.amount_msat)
       +            self.log[-subject]['settles'].clear()
       +
       +            return old_amount - htlcsum(self.htlcs(subject, False))
       +
       +        sent_this_batch = mark_settled(LOCAL)
       +        received_this_batch = mark_settled(REMOTE)
       +
       +        next_point = self.config[REMOTE].next_per_commitment_point
       +
       +        print("RECEIVED", received_this_batch)
       +        print("SENT", sent_this_batch)
       +        self.config[REMOTE]=self.config[REMOTE]._replace(
       +            ctn=self.config[REMOTE].ctn + 1,
       +            current_per_commitment_point=next_point,
       +            next_per_commitment_point=revocation.next_per_commitment_point,
       +            amount_msat=self.config[REMOTE].amount_msat + (sent_this_batch - received_this_batch)
       +        )
       +        self.config[LOCAL]=self.config[LOCAL]._replace(
       +            amount_msat = self.config[LOCAL].amount_msat + (received_this_batch - sent_this_batch)
       +        )
       +
       +        for pending_fee in self.fee_mgr:
       +            if self.constraints.is_initiator:
       +                pending_fee[FUNDEE_ACKED] = True
       +
       +        self.local_commitment = self.pending_local_commitment
       +        self.remote_commitment = self.pending_remote_commitment
       +        self.remote_commitment_to_be_revoked = prev_remote_commitment
       +        return received_this_batch, sent_this_batch
       +
       +    def balance(self, subject):
       +        initial = self.config[subject].initial_msat
       +
       +        initial -= sum(self.settled[subject])
       +        initial += sum(self.settled[-subject])
       +
       +        assert initial == self.config[subject].amount_msat
       +        return initial
       +
       +    def amounts(self):
       +        remote_settled= htlcsum(self.htlcs(REMOTE, False))
       +        local_settled= htlcsum(self.htlcs(LOCAL, False))
       +        unsettled_local = htlcsum(self.htlcs(LOCAL, True))
       +        unsettled_remote = htlcsum(self.htlcs(REMOTE, True))
       +        remote_msat = self.config[REMOTE].amount_msat -\
       +          unsettled_remote + local_settled - remote_settled
       +        local_msat = self.config[LOCAL].amount_msat -\
       +          unsettled_local + remote_settled - local_settled
       +        return remote_msat, local_msat
       +
       +    def included_htlcs(self, subject, htlc_initiator):
       +        """
       +        return filter of non-dust htlcs for subjects commitment transaction, initiated by given party
       +        """
       +        feerate = self.pending_feerate(subject)
       +        conf = self.config[subject]
       +        weight = HTLC_SUCCESS_WEIGHT if subject != htlc_initiator else HTLC_TIMEOUT_WEIGHT
       +        htlcs = self.htlcs(htlc_initiator, only_pending=True)
       +        fee_for_htlc = lambda htlc: htlc.amount_msat // 1000 - (weight * feerate // 1000)
       +        return filter(lambda htlc: fee_for_htlc(htlc) >= conf.dust_limit_sat, htlcs)
       +
       +    @property
       +    def pending_remote_commitment(self):
       +        this_point = self.config[REMOTE].next_per_commitment_point
       +        return self.make_commitment(REMOTE, this_point)
       +
       +    def pending_feerate(self, subject):
       +        candidate = self.constraints.feerate
       +        for pending_fee in self.fee_mgr:
       +            x = pending_fee.pending_feerate(subject)
       +            if x is not None:
       +                candidate = x
       +
       +        return candidate
       +
       +    @property
       +    def pending_local_commitment(self):
       +        _, this_point, _ = self.points
       +        return self.make_commitment(LOCAL, this_point)
       +
       +    def total_msat(self, sub):
       +        return sum(self.settled[sub])
       +
       +    def htlcs(self, subject, only_pending):
       +        """
       +        only_pending: require the htlc's settlement to be pending (needs additional signatures/acks)
       +        """
       +        update_log = self.log[subject]
       +        other_log = self.log[-subject]
       +        res = []
       +        for htlc in update_log['adds'].values():
       +            locked_in = htlc.locked_in[subject]
       +
       +            if locked_in is None or only_pending == (htlc.htlc_id in other_log['settles']):
       +                continue
       +            res.append(htlc)
       +        return res
       +
       +    def settle_htlc(self, preimage, htlc_id):
       +        """
       +        SettleHTLC attempts to settle an existing outstanding received HTLC.
       +        """
       +        self.print_error("settle_htlc")
       +        htlc = self.log[REMOTE]['adds'][htlc_id]
       +        assert htlc.payment_hash == sha256(preimage)
       +        self.log[LOCAL]['settles'].append(htlc_id)
       +
       +    def receive_htlc_settle(self, preimage, htlc_index):
       +        self.print_error("receive_htlc_settle")
       +        htlc = self.log[LOCAL]['adds'][htlc_index]
       +        assert htlc.payment_hash == sha256(preimage)
       +        self.log[REMOTE]['settles'].append(htlc_index)
       +
       +    def receive_fail_htlc(self, htlc_id):
       +        self.print_error("receive_fail_htlc")
       +        self.log[LOCAL]['adds'].pop(htlc_id)
       +
       +    @property
       +    def current_height(self):
       +        return {LOCAL: self.config[LOCAL].ctn, REMOTE: self.config[REMOTE].ctn}
       +
       +    @property
       +    def pending_local_fee(self):
       +        return self.constraints.capacity - sum(x[2] for x in self.pending_local_commitment.outputs())
       +
       +    def update_fee(self, feerate):
       +        if not self.constraints.is_initiator:
       +            raise Exception("only initiator can update_fee, this counterparty is not initiator")
       +        pending_fee = FeeUpdate(self, rate=feerate)
       +        self.fee_mgr.append(pending_fee)
       +
       +    def receive_update_fee(self, feerate):
       +        if self.constraints.is_initiator:
       +            raise Exception("only the non-initiator can receive_update_fee, this counterparty is initiator")
       +        pending_fee = FeeUpdate(self, rate=feerate)
       +        self.fee_mgr.append(pending_fee)
       +
       +    def remove_uncommitted_htlcs_from_log(self, subject):
       +        """
       +        returns
       +        - the htlcs with uncommited (not locked in) htlcs removed
       +        - a list of htlc_ids that were removed
       +        """
       +        removed = []
       +        htlcs = []
       +        for i in self.log[subject]['adds'].values():
       +            locked_in = i.locked_in[LOCAL] is not None or i.locked_in[REMOTE] is not None
       +            if locked_in:
       +                htlcs.append(i._asdict())
       +            else:
       +                removed.append(i.htlc_id)
       +        return htlcs, removed
       +
       +    def to_save(self):
       +        # need to forget about uncommited htlcs
       +        # since we must assume they don't know about it,
       +        # if it was not acked
       +        remote_filtered, remote_removed = self.remove_uncommitted_htlcs_from_log(REMOTE)
       +        local_filtered, local_removed = self.remove_uncommitted_htlcs_from_log(LOCAL)
       +        to_save = {
       +                "local_config": self.config[LOCAL],
       +                "remote_config": self.config[REMOTE],
       +                "channel_id": self.channel_id,
       +                "short_channel_id": self.short_channel_id,
       +                "constraints": self.constraints,
       +                "funding_outpoint": self.funding_outpoint,
       +                "node_id": self.node_id,
       +                "remote_commitment_to_be_revoked": str(self.remote_commitment_to_be_revoked),
       +                "remote_log": remote_filtered,
       +                "local_log": local_filtered,
       +                "onion_keys": {str(k): bh2u(v) for k, v in self.onion_keys.items()},
       +                "settled_local": self.settled[LOCAL],
       +                "settled_remote": self.settled[REMOTE],
       +        }
       +
       +        # htlcs number must be monotonically increasing,
       +        # so we have to decrease the counter
       +        if len(remote_removed) != 0:
       +            assert min(remote_removed) < to_save['remote_config'].next_htlc_id
       +            to_save['remote_config'] = to_save['remote_config']._replace(next_htlc_id = min(remote_removed))
       +
       +        if len(local_removed) != 0:
       +            assert min(local_removed) < to_save['local_config'].next_htlc_id
       +            to_save['local_config'] = to_save['local_config']._replace(next_htlc_id = min(local_removed))
       +
       +        return to_save
       +
       +    def serialize(self):
       +        namedtuples_to_dict = lambda v: {i: j._asdict() if isinstance(j, tuple) else j for i, j in v._asdict().items()}
       +        serialized_channel = {k: namedtuples_to_dict(v) if isinstance(v, tuple) else v for k, v in self.to_save().items()}
       +        class MyJsonEncoder(json.JSONEncoder):
       +            def default(self, o):
       +                if isinstance(o, bytes):
       +                    return binascii.hexlify(o).decode("ascii")
       +                if isinstance(o, RevocationStore):
       +                    return o.serialize()
       +                return super(MyJsonEncoder, self)
       +        dumped = MyJsonEncoder().encode(serialized_channel)
       +        roundtripped = json.loads(dumped)
       +        reconstructed = Channel(roundtripped)
       +        if reconstructed.to_save() != self.to_save():
       +            from pprint import pformat
       +            try:
       +                from deepdiff import DeepDiff
       +            except ImportError:
       +                raise Exception("Channels did not roundtrip serialization without changes:\n" + pformat(reconstructed.to_save()) + "\n" + pformat(self.to_save()))
       +            else:
       +                raise Exception("Channels did not roundtrip serialization without changes:\n" + pformat(DeepDiff(reconstructed.to_save(), self.to_save())))
       +        return roundtripped
       +
       +    def __str__(self):
       +        return self.serialize()
       +
       +    def make_commitment(self, subject, this_point) -> Transaction:
       +        remote_msat, local_msat = self.amounts()
       +        assert local_msat >= 0
       +        assert remote_msat >= 0
       +        this_config = self.config[subject]
       +        other_config = self.config[-subject]
       +        other_htlc_pubkey = derive_pubkey(other_config.htlc_basepoint.pubkey, this_point)
       +        this_htlc_pubkey = derive_pubkey(this_config.htlc_basepoint.pubkey, this_point)
       +        other_revocation_pubkey = derive_blinded_pubkey(other_config.revocation_basepoint.pubkey, this_point)
       +        htlcs = []
       +        for htlc in self.included_htlcs(subject, -subject):
       +            htlcs.append( ScriptHtlc( make_received_htlc(
       +                other_revocation_pubkey,
       +                other_htlc_pubkey,
       +                this_htlc_pubkey,
       +                htlc.payment_hash,
       +                htlc.cltv_expiry), htlc))
       +        for htlc in self.included_htlcs(subject, subject):
       +            htlcs.append(
       +                ScriptHtlc( make_offered_htlc(
       +                    other_revocation_pubkey,
       +                    other_htlc_pubkey,
       +                    this_htlc_pubkey,
       +                    htlc.payment_hash), htlc))
       +        if subject != LOCAL:
       +            remote_msat, local_msat = local_msat, remote_msat
       +        payment_pubkey = derive_pubkey(other_config.payment_basepoint.pubkey, this_point)
       +        return make_commitment(
       +            self.config[subject].ctn + 1,
       +            this_config.multisig_key.pubkey,
       +            other_config.multisig_key.pubkey,
       +            payment_pubkey,
       +            self.config[LOCAL].payment_basepoint.pubkey,
       +            self.config[REMOTE].payment_basepoint.pubkey,
       +            other_revocation_pubkey,
       +            derive_pubkey(this_config.delayed_basepoint.pubkey, this_point),
       +            other_config.to_self_delay,
       +            *self.funding_outpoint,
       +            self.constraints.capacity,
       +            local_msat,
       +            remote_msat,
       +            this_config.dust_limit_sat,
       +            self.pending_feerate(subject),
       +            subject == LOCAL,
       +            self.constraints.is_initiator,
       +            htlcs=htlcs)
       +
       +    def make_closing_tx(self, local_script: bytes, remote_script: bytes, fee_sat: Optional[int] = None) -> (bytes, int):
       +        if fee_sat is None:
       +            fee_sat = self.pending_local_fee
       +
       +        _, outputs = make_outputs(fee_sat * 1000, True,
       +                self.config[LOCAL].amount_msat,
       +                self.config[REMOTE].amount_msat,
       +                (TYPE_SCRIPT, bh2u(local_script)),
       +                (TYPE_SCRIPT, bh2u(remote_script)),
       +                [], self.config[LOCAL].dust_limit_sat)
       +
       +        closing_tx = make_closing_tx(self.config[LOCAL].multisig_key.pubkey,
       +                self.config[REMOTE].multisig_key.pubkey,
       +                self.config[LOCAL].payment_basepoint.pubkey,
       +                self.config[REMOTE].payment_basepoint.pubkey,
       +                # TODO hardcoded we_are_initiator:
       +                True, *self.funding_outpoint, self.constraints.capacity,
       +                outputs)
       +
       +        der_sig = bfh(closing_tx.sign_txin(0, self.config[LOCAL].multisig_key.privkey))
       +        sig = ecc.sig_string_from_der_sig(der_sig[:-1])
       +        return sig, fee_sat
       +
       +def maybe_create_sweeptx_for_their_ctx_to_remote(chan, ctx, their_pcp: bytes,
       +                                                 sweep_address) -> Optional[EncumberedTransaction]:
       +    assert isinstance(their_pcp, bytes)
       +    payment_bp_privkey = ecc.ECPrivkey(chan.config[LOCAL].payment_basepoint.privkey)
       +    our_payment_privkey = derive_privkey(payment_bp_privkey.secret_scalar, their_pcp)
       +    our_payment_privkey = ecc.ECPrivkey.from_secret_scalar(our_payment_privkey)
       +    our_payment_pubkey = our_payment_privkey.get_public_key_bytes(compressed=True)
       +    to_remote_address = make_commitment_output_to_remote_address(our_payment_pubkey)
       +    for output_idx, (type_, addr, val) in enumerate(ctx.outputs()):
       +        if type_ == TYPE_ADDRESS and addr == to_remote_address:
       +            break
       +    else:
       +        return None
       +    sweep_tx = create_sweeptx_their_ctx_to_remote(address=sweep_address,
       +                                                  ctx=ctx,
       +                                                  output_idx=output_idx,
       +                                                  our_payment_privkey=our_payment_privkey)
       +    return EncumberedTransaction(sweep_tx, csv_delay=0)
       +
       +
       +def maybe_create_sweeptx_for_their_ctx_to_local(chan, ctx, per_commitment_secret: bytes,
       +                                                sweep_address) -> Optional[EncumberedTransaction]:
       +    assert isinstance(per_commitment_secret, bytes)
       +    per_commitment_point = ecc.ECPrivkey(per_commitment_secret).get_public_key_bytes(compressed=True)
       +    revocation_privkey = derive_blinded_privkey(chan.config[LOCAL].revocation_basepoint.privkey,
       +                                                per_commitment_secret)
       +    revocation_pubkey = ecc.ECPrivkey(revocation_privkey).get_public_key_bytes(compressed=True)
       +    to_self_delay = chan.config[LOCAL].to_self_delay
       +    delayed_pubkey = derive_pubkey(chan.config[REMOTE].delayed_basepoint.pubkey,
       +                                   per_commitment_point)
       +    witness_script = bh2u(make_commitment_output_to_local_witness_script(
       +        revocation_pubkey, to_self_delay, delayed_pubkey))
       +    to_local_address = redeem_script_to_address('p2wsh', witness_script)
       +    for output_idx, o in enumerate(ctx.outputs()):
       +        if o.type == TYPE_ADDRESS and o.address == to_local_address:
       +            break
       +    else:
       +        return None
       +    sweep_tx = create_sweeptx_ctx_to_local(address=sweep_address,
       +                                           ctx=ctx,
       +                                           output_idx=output_idx,
       +                                           witness_script=witness_script,
       +                                           privkey=revocation_privkey,
       +                                           is_revocation=True)
       +    return EncumberedTransaction(sweep_tx, csv_delay=0)
       +
       +
       +def maybe_create_sweeptx_for_our_ctx_to_local(chan, ctx, our_pcp: bytes,
       +                                              sweep_address) -> Optional[EncumberedTransaction]:
       +    assert isinstance(our_pcp, bytes)
       +    delayed_bp_privkey = ecc.ECPrivkey(chan.config[LOCAL].delayed_basepoint.privkey)
       +    our_localdelayed_privkey = derive_privkey(delayed_bp_privkey.secret_scalar, our_pcp)
       +    our_localdelayed_privkey = ecc.ECPrivkey.from_secret_scalar(our_localdelayed_privkey)
       +    our_localdelayed_pubkey = our_localdelayed_privkey.get_public_key_bytes(compressed=True)
       +    revocation_pubkey = derive_blinded_pubkey(chan.config[REMOTE].revocation_basepoint.pubkey,
       +                                              our_pcp)
       +    to_self_delay = chan.config[REMOTE].to_self_delay
       +    witness_script = bh2u(make_commitment_output_to_local_witness_script(
       +        revocation_pubkey, to_self_delay, our_localdelayed_pubkey))
       +    to_local_address = redeem_script_to_address('p2wsh', witness_script)
       +    for output_idx, o in enumerate(ctx.outputs()):
       +        if o.type == TYPE_ADDRESS and o.address == to_local_address:
       +            break
       +    else:
       +        return None
       +    sweep_tx = create_sweeptx_ctx_to_local(address=sweep_address,
       +                                           ctx=ctx,
       +                                           output_idx=output_idx,
       +                                           witness_script=witness_script,
       +                                           privkey=our_localdelayed_privkey.get_secret_bytes(),
       +                                           is_revocation=False,
       +                                           to_self_delay=to_self_delay)
       +
       +    return EncumberedTransaction(sweep_tx, csv_delay=to_self_delay)
       +
       +
       +def create_sweeptx_their_ctx_to_remote(address, ctx, output_idx: int, our_payment_privkey: ecc.ECPrivkey,
       +                                       fee_per_kb: int=None) -> Transaction:
       +    our_payment_pubkey = our_payment_privkey.get_public_key_hex(compressed=True)
       +    val = ctx.outputs()[output_idx].value
       +    sweep_inputs = [{
       +        'type': 'p2wpkh',
       +        'x_pubkeys': [our_payment_pubkey],
       +        'num_sig': 1,
       +        'prevout_n': output_idx,
       +        'prevout_hash': ctx.txid(),
       +        'value': val,
       +        'coinbase': False,
       +        'signatures': [None],
       +    }]
       +    tx_size_bytes = 110  # approx size of p2wpkh->p2wpkh
       +    if fee_per_kb is None: fee_per_kb = FEERATE_FALLBACK_STATIC_FEE
       +    fee = SimpleConfig.estimate_fee_for_feerate(fee_per_kb, tx_size_bytes)
       +    sweep_outputs = [TxOutput(TYPE_ADDRESS, address, val-fee)]
       +    sweep_tx = Transaction.from_io(sweep_inputs, sweep_outputs)
       +    sweep_tx.set_rbf(True)
       +    sweep_tx.sign({our_payment_pubkey: (our_payment_privkey.get_secret_bytes(), True)})
       +    if not sweep_tx.is_complete():
       +        raise Exception('channel close sweep tx is not complete')
       +    return sweep_tx
       +
       +
       +def create_sweeptx_ctx_to_local(address, ctx, output_idx: int, witness_script: str,
       +                                privkey: bytes, is_revocation: bool,
       +                                to_self_delay: int=None,
       +                                fee_per_kb: int=None) -> Transaction:
       +    """Create a txn that sweeps the 'to_local' output of a commitment
       +    transaction into our wallet.
       +
       +    privkey: either revocation_privkey or localdelayed_privkey
       +    is_revocation: tells us which ^
       +    """
       +    val = ctx.outputs()[output_idx].value
       +    sweep_inputs = [{
       +        'scriptSig': '',
       +        'type': 'p2wsh',
       +        'signatures': [],
       +        'num_sig': 0,
       +        'prevout_n': output_idx,
       +        'prevout_hash': ctx.txid(),
       +        'value': val,
       +        'coinbase': False,
       +        'preimage_script': witness_script,
       +    }]
       +    if to_self_delay is not None:
       +        sweep_inputs[0]['sequence'] = to_self_delay
       +    tx_size_bytes = 121  # approx size of to_local -> p2wpkh
       +    if fee_per_kb is None: fee_per_kb = FEERATE_FALLBACK_STATIC_FEE
       +    fee = SimpleConfig.estimate_fee_for_feerate(fee_per_kb, tx_size_bytes)
       +    sweep_outputs = [TxOutput(TYPE_ADDRESS, address, val - fee)]
       +    sweep_tx = Transaction.from_io(sweep_inputs, sweep_outputs, version=2)
       +    sig = sweep_tx.sign_txin(0, privkey)
       +    witness = construct_witness([sig, int(is_revocation), witness_script])
       +    sweep_tx.inputs()[0]['witness'] = witness
       +    return sweep_tx
   DIR diff --git a/electrum/lnhtlc.py b/electrum/lnhtlc.py
       t@@ -1,810 +0,0 @@
       -# ported from lnd 42de4400bff5105352d0552155f73589166d162b
       -from collections import namedtuple, defaultdict
       -import binascii
       -import json
       -from enum import Enum, auto
       -from typing import Optional
       -
       -from .util import bfh, PrintError, bh2u
       -from .bitcoin import Hash, TYPE_SCRIPT, TYPE_ADDRESS
       -from .bitcoin import redeem_script_to_address
       -from .crypto import sha256
       -from . import ecc
       -from .lnutil import Outpoint, LocalConfig, RemoteConfig, Keypair, OnlyPubkeyKeypair, ChannelConstraints, RevocationStore, EncumberedTransaction
       -from .lnutil import get_per_commitment_secret_from_seed
       -from .lnutil import make_commitment_output_to_remote_address, make_commitment_output_to_local_witness_script
       -from .lnutil import secret_to_pubkey, derive_privkey, derive_pubkey, derive_blinded_pubkey, derive_blinded_privkey
       -from .lnutil import sign_and_get_sig_string
       -from .lnutil import make_htlc_tx_with_open_channel, make_commitment, make_received_htlc, make_offered_htlc
       -from .lnutil import HTLC_TIMEOUT_WEIGHT, HTLC_SUCCESS_WEIGHT
       -from .lnutil import funding_output_script, LOCAL, REMOTE, HTLCOwner, make_closing_tx, make_outputs
       -from .lnutil import ScriptHtlc, SENT, RECEIVED
       -from .transaction import Transaction, TxOutput, construct_witness
       -from .simple_config import SimpleConfig, FEERATE_FALLBACK_STATIC_FEE
       -
       -
       -RevokeAndAck = namedtuple("RevokeAndAck", ["per_commitment_secret", "next_per_commitment_point"])
       -
       -class FeeUpdateProgress(Enum):
       -    FUNDEE_SIGNED = auto()
       -    FUNDEE_ACKED =  auto()
       -    FUNDER_SIGNED = auto()
       -
       -FUNDEE_SIGNED = FeeUpdateProgress.FUNDEE_SIGNED
       -FUNDEE_ACKED = FeeUpdateProgress.FUNDEE_ACKED
       -FUNDER_SIGNED = FeeUpdateProgress.FUNDER_SIGNED
       -
       -class FeeUpdate(defaultdict):
       -    def __init__(self, chan, rate):
       -        super().__init__(lambda: False)
       -        self.rate = rate
       -        self.chan = chan
       -
       -    def pending_feerate(self, subject):
       -        if self[FUNDEE_ACKED]:
       -            return self.rate
       -        if subject == REMOTE and self.chan.constraints.is_initiator:
       -            return self.rate
       -        if subject == LOCAL and not self.chan.constraints.is_initiator:
       -            return self.rate
       -        # implicit return None
       -
       -class UpdateAddHtlc(namedtuple('UpdateAddHtlc', ['amount_msat', 'payment_hash', 'cltv_expiry', 'locked_in', 'htlc_id'])):
       -    __slots__ = ()
       -    def __new__(cls, *args, **kwargs):
       -        if len(args) > 0:
       -            args = list(args)
       -            if type(args[1]) is str:
       -                args[1] = bfh(args[1])
       -            args[3] = {HTLCOwner(int(x)): y for x,y in args[3].items()}
       -            return super().__new__(cls, *args)
       -        if type(kwargs['payment_hash']) is str:
       -            kwargs['payment_hash'] = bfh(kwargs['payment_hash'])
       -        if 'locked_in' not in kwargs:
       -            kwargs['locked_in'] = {LOCAL: None, REMOTE: None}
       -        else:
       -            kwargs['locked_in'] = {HTLCOwner(int(x)): y for x,y in kwargs['locked_in'].items()}
       -        return super().__new__(cls, **kwargs)
       -
       -def decodeAll(d, local):
       -    for k, v in d.items():
       -        if k == 'revocation_store':
       -            yield (k, RevocationStore.from_json_obj(v))
       -        elif k.endswith("_basepoint") or k.endswith("_key"):
       -            if local:
       -                yield (k, Keypair(**dict(decodeAll(v, local))))
       -            else:
       -                yield (k, OnlyPubkeyKeypair(**dict(decodeAll(v, local))))
       -        elif k in ["node_id", "channel_id", "short_channel_id", "pubkey", "privkey", "current_per_commitment_point", "next_per_commitment_point", "per_commitment_secret_seed", "current_commitment_signature", "current_htlc_signatures"] and v is not None:
       -            yield (k, binascii.unhexlify(v))
       -        else:
       -            yield (k, v)
       -
       -def htlcsum(htlcs):
       -    return sum([x.amount_msat for x in htlcs])
       -
       -class HTLCStateMachine(PrintError):
       -    def diagnostic_name(self):
       -        return str(self.name)
       -
       -    def __init__(self, state, name = None):
       -        assert 'local_state' not in state
       -        self.config = {}
       -        self.config[LOCAL] = state["local_config"]
       -        if type(self.config[LOCAL]) is not LocalConfig:
       -            conf = dict(decodeAll(self.config[LOCAL], True))
       -            self.config[LOCAL] = LocalConfig(**conf)
       -        assert type(self.config[LOCAL].htlc_basepoint.privkey) is bytes
       -
       -        self.config[REMOTE] = state["remote_config"]
       -        if type(self.config[REMOTE]) is not RemoteConfig:
       -            conf = dict(decodeAll(self.config[REMOTE], False))
       -            self.config[REMOTE] = RemoteConfig(**conf)
       -        assert type(self.config[REMOTE].htlc_basepoint.pubkey) is bytes
       -
       -        self.channel_id = bfh(state["channel_id"]) if type(state["channel_id"]) not in (bytes, type(None)) else state["channel_id"]
       -        self.constraints = ChannelConstraints(**state["constraints"]) if type(state["constraints"]) is not ChannelConstraints else state["constraints"]
       -        self.funding_outpoint = Outpoint(**dict(decodeAll(state["funding_outpoint"], False))) if type(state["funding_outpoint"]) is not Outpoint else state["funding_outpoint"]
       -        self.node_id = bfh(state["node_id"]) if type(state["node_id"]) not in (bytes, type(None)) else state["node_id"]
       -        self.short_channel_id = bfh(state["short_channel_id"]) if type(state["short_channel_id"]) not in (bytes, type(None)) else state["short_channel_id"]
       -        self.short_channel_id_predicted = self.short_channel_id
       -        self.onion_keys = {int(k): bfh(v) for k,v in state['onion_keys'].items()} if 'onion_keys' in state else {}
       -
       -        # FIXME this is a tx serialised in the custom electrum partial tx format.
       -        # we should not persist txns in this format. we should persist htlcs, and be able to derive
       -        # any past commitment transaction and use that instead; until then...
       -        self.remote_commitment_to_be_revoked = Transaction(state["remote_commitment_to_be_revoked"])
       -
       -        template = lambda: {'adds': {}, 'settles': []}
       -        self.log = {LOCAL: template(), REMOTE: template()}
       -        for strname, subject in [('remote_log', REMOTE), ('local_log', LOCAL)]:
       -            if strname not in state: continue
       -            for y in state[strname]:
       -                htlc = UpdateAddHtlc(**y)
       -                self.log[subject]['adds'][htlc.htlc_id] = htlc
       -
       -        self.name = name
       -
       -        self.fee_mgr = []
       -
       -        self.local_commitment = self.pending_local_commitment
       -        self.remote_commitment = self.pending_remote_commitment
       -
       -        self._is_funding_txo_spent = None  # "don't know"
       -        self.set_state('DISCONNECTED')
       -
       -        self.lnwatcher = None
       -
       -        self.settled = {LOCAL: state.get('settled_local', []), REMOTE: state.get('settled_remote', [])}
       -
       -    def set_state(self, state: str):
       -        self._state = state
       -
       -    def get_state(self):
       -        return self._state
       -
       -    def set_funding_txo_spentness(self, is_spent: bool):
       -        assert isinstance(is_spent, bool)
       -        self._is_funding_txo_spent = is_spent
       -
       -    def should_try_to_reestablish_peer(self) -> bool:
       -        return self._is_funding_txo_spent is False and self._state == 'DISCONNECTED'
       -
       -    def get_funding_address(self):
       -        script = funding_output_script(self.config[LOCAL], self.config[REMOTE])
       -        return redeem_script_to_address('p2wsh', script)
       -
       -    def add_htlc(self, htlc):
       -        """
       -        AddHTLC adds an HTLC to the state machine's local update log. This method
       -        should be called when preparing to send an outgoing HTLC.
       -        """
       -        assert type(htlc) is dict
       -        htlc = UpdateAddHtlc(**htlc, htlc_id=self.config[LOCAL].next_htlc_id)
       -        self.log[LOCAL]['adds'][htlc.htlc_id] = htlc
       -        self.print_error("add_htlc")
       -        self.config[LOCAL]=self.config[LOCAL]._replace(next_htlc_id=htlc.htlc_id + 1)
       -        return htlc.htlc_id
       -
       -    def receive_htlc(self, htlc):
       -        """
       -        ReceiveHTLC adds an HTLC to the state machine's remote update log. This
       -        method should be called in response to receiving a new HTLC from the remote
       -        party.
       -        """
       -        assert type(htlc) is dict
       -        htlc = UpdateAddHtlc(**htlc, htlc_id = self.config[REMOTE].next_htlc_id)
       -        self.log[REMOTE]['adds'][htlc.htlc_id] = htlc
       -        self.print_error("receive_htlc")
       -        self.config[REMOTE]=self.config[REMOTE]._replace(next_htlc_id=htlc.htlc_id + 1)
       -        return htlc.htlc_id
       -
       -    def sign_next_commitment(self):
       -        """
       -        SignNextCommitment signs a new commitment which includes any previous
       -        unsettled HTLCs, any new HTLCs, and any modifications to prior HTLCs
       -        committed in previous commitment updates. Signing a new commitment
       -        decrements the available revocation window by 1. After a successful method
       -        call, the remote party's commitment chain is extended by a new commitment
       -        which includes all updates to the HTLC log prior to this method invocation.
       -        The first return parameter is the signature for the commitment transaction
       -        itself, while the second parameter is a slice of all HTLC signatures (if
       -        any). The HTLC signatures are sorted according to the BIP 69 order of the
       -        HTLC's on the commitment transaction.
       -        """
       -        for htlc in self.log[LOCAL]['adds'].values():
       -            if htlc.locked_in[LOCAL] is None:
       -                htlc.locked_in[LOCAL] = self.config[LOCAL].ctn
       -        self.print_error("sign_next_commitment")
       -
       -        pending_remote_commitment = self.pending_remote_commitment
       -        sig_64 = sign_and_get_sig_string(pending_remote_commitment, self.config[LOCAL], self.config[REMOTE])
       -
       -        their_remote_htlc_privkey_number = derive_privkey(
       -            int.from_bytes(self.config[LOCAL].htlc_basepoint.privkey, 'big'),
       -            self.config[REMOTE].next_per_commitment_point)
       -        their_remote_htlc_privkey = their_remote_htlc_privkey_number.to_bytes(32, 'big')
       -
       -        for_us = False
       -
       -        htlcsigs = []
       -        for we_receive, htlcs in zip([True, False], [self.included_htlcs(REMOTE, REMOTE), self.included_htlcs(REMOTE, LOCAL)]):
       -            for htlc in htlcs:
       -                args = [self.config[REMOTE].next_per_commitment_point, for_us, we_receive, pending_remote_commitment, htlc]
       -                htlc_tx = make_htlc_tx_with_open_channel(self, *args)
       -                sig = bfh(htlc_tx.sign_txin(0, their_remote_htlc_privkey))
       -                htlc_sig = ecc.sig_string_from_der_sig(sig[:-1])
       -                htlcsigs.append((pending_remote_commitment.htlc_output_indices[htlc.payment_hash], htlc_sig))
       -
       -        for pending_fee in self.fee_mgr:
       -            if not self.constraints.is_initiator:
       -                pending_fee[FUNDEE_SIGNED] = True
       -            if self.constraints.is_initiator and pending_fee[FUNDEE_ACKED]:
       -                pending_fee[FUNDER_SIGNED] = True
       -
       -        self.process_new_offchain_ctx(pending_remote_commitment, ours=False)
       -
       -        htlcsigs.sort()
       -        htlcsigs = [x[1] for x in htlcsigs]
       -
       -        return sig_64, htlcsigs
       -
       -    def receive_new_commitment(self, sig, htlc_sigs):
       -        """
       -        ReceiveNewCommitment process a signature for a new commitment state sent by
       -        the remote party. This method should be called in response to the
       -        remote party initiating a new change, or when the remote party sends a
       -        signature fully accepting a new state we've initiated. If we are able to
       -        successfully validate the signature, then the generated commitment is added
       -        to our local commitment chain. Once we send a revocation for our prior
       -        state, then this newly added commitment becomes our current accepted channel
       -        state.
       -        """
       -
       -        self.print_error("receive_new_commitment")
       -        for htlc in self.log[REMOTE]['adds'].values():
       -            if htlc.locked_in[REMOTE] is None:
       -                htlc.locked_in[REMOTE] = self.config[REMOTE].ctn
       -        assert len(htlc_sigs) == 0 or type(htlc_sigs[0]) is bytes
       -
       -        pending_local_commitment = self.pending_local_commitment
       -        preimage_hex = pending_local_commitment.serialize_preimage(0)
       -        pre_hash = Hash(bfh(preimage_hex))
       -        if not ecc.verify_signature(self.config[REMOTE].multisig_key.pubkey, sig, pre_hash):
       -            raise Exception('failed verifying signature of our updated commitment transaction: ' + bh2u(sig) + ' preimage is ' + preimage_hex)
       -
       -        _, this_point, _ = self.points
       -
       -        for htlcs, we_receive in [(self.included_htlcs(LOCAL, REMOTE), True), (self.included_htlcs(LOCAL, LOCAL), False)]:
       -            for htlc in htlcs:
       -                htlc_tx = make_htlc_tx_with_open_channel(self, this_point, True, we_receive, pending_local_commitment, htlc)
       -                pre_hash = Hash(bfh(htlc_tx.serialize_preimage(0)))
       -                remote_htlc_pubkey = derive_pubkey(self.config[REMOTE].htlc_basepoint.pubkey, this_point)
       -                for idx, sig in enumerate(htlc_sigs):
       -                    if ecc.verify_signature(remote_htlc_pubkey, sig, pre_hash):
       -                        del htlc_sigs[idx]
       -                        break
       -                else:
       -                    raise Exception(f'failed verifying HTLC signatures: {htlc}')
       -        if len(htlc_sigs) != 0: # all sigs should have been popped above
       -            raise Exception('failed verifying HTLC signatures: invalid amount of correct signatures')
       -
       -        for pending_fee in self.fee_mgr:
       -            if not self.constraints.is_initiator:
       -                pending_fee[FUNDEE_SIGNED] = True
       -            if self.constraints.is_initiator and pending_fee[FUNDEE_ACKED]:
       -                pending_fee[FUNDER_SIGNED] = True
       -
       -        self.process_new_offchain_ctx(pending_local_commitment, ours=True)
       -
       -
       -    def revoke_current_commitment(self):
       -        """
       -        RevokeCurrentCommitment revokes the next lowest unrevoked commitment
       -        transaction in the local commitment chain. As a result the edge of our
       -        revocation window is extended by one, and the tail of our local commitment
       -        chain is advanced by a single commitment. This now lowest unrevoked
       -        commitment becomes our currently accepted state within the channel. This
       -        method also returns the set of HTLC's currently active within the commitment
       -        transaction. This return value allows callers to act once an HTLC has been
       -        locked into our commitment transaction.
       -        """
       -        self.print_error("revoke_current_commitment")
       -
       -        last_secret, this_point, next_point = self.points
       -
       -        new_feerate = self.constraints.feerate
       -
       -        for pending_fee in self.fee_mgr[:]:
       -            if not self.constraints.is_initiator and pending_fee[FUNDEE_SIGNED]:
       -                new_feerate = pending_fee.rate
       -                self.fee_mgr.remove(pending_fee)
       -                print("FEERATE CHANGE COMPLETE (non-initiator)")
       -            if self.constraints.is_initiator and pending_fee[FUNDER_SIGNED]:
       -                new_feerate = pending_fee.rate
       -                self.fee_mgr.remove(pending_fee)
       -                print("FEERATE CHANGE COMPLETE (initiator)")
       -
       -        self.config[LOCAL]=self.config[LOCAL]._replace(
       -            ctn=self.config[LOCAL].ctn + 1,
       -        )
       -        self.constraints=self.constraints._replace(
       -            feerate=new_feerate
       -        )
       -
       -        self.local_commitment = self.pending_local_commitment
       -
       -        return RevokeAndAck(last_secret, next_point), "current htlcs"
       -
       -    @property
       -    def points(self):
       -        last_small_num = self.config[LOCAL].ctn
       -        this_small_num = last_small_num + 1
       -        next_small_num = last_small_num + 2
       -        last_secret = get_per_commitment_secret_from_seed(self.config[LOCAL].per_commitment_secret_seed, RevocationStore.START_INDEX - last_small_num)
       -        this_secret = get_per_commitment_secret_from_seed(self.config[LOCAL].per_commitment_secret_seed, RevocationStore.START_INDEX - this_small_num)
       -        this_point = secret_to_pubkey(int.from_bytes(this_secret, 'big'))
       -        next_secret = get_per_commitment_secret_from_seed(self.config[LOCAL].per_commitment_secret_seed, RevocationStore.START_INDEX - next_small_num)
       -        next_point = secret_to_pubkey(int.from_bytes(next_secret, 'big'))
       -        return last_secret, this_point, next_point
       -
       -    # TODO batch sweeps
       -    # TODO sweep HTLC outputs
       -    def process_new_offchain_ctx(self, ctx, ours: bool):
       -        if not self.lnwatcher:
       -            return
       -        outpoint = self.funding_outpoint.to_str()
       -        if ours:
       -            ctn = self.config[LOCAL].ctn + 1
       -            our_per_commitment_secret = get_per_commitment_secret_from_seed(
       -                self.config[LOCAL].per_commitment_secret_seed, RevocationStore.START_INDEX - ctn)
       -            our_cur_pcp = ecc.ECPrivkey(our_per_commitment_secret).get_public_key_bytes(compressed=True)
       -            encumbered_sweeptx = maybe_create_sweeptx_for_our_ctx_to_local(self, ctx, our_cur_pcp, self.sweep_address)
       -        else:
       -            their_cur_pcp = self.config[REMOTE].next_per_commitment_point
       -            encumbered_sweeptx = maybe_create_sweeptx_for_their_ctx_to_remote(self, ctx, their_cur_pcp, self.sweep_address)
       -        self.lnwatcher.add_sweep_tx(outpoint, ctx.txid(), encumbered_sweeptx)
       -
       -    def process_new_revocation_secret(self, per_commitment_secret: bytes):
       -        if not self.lnwatcher:
       -            return
       -        outpoint = self.funding_outpoint.to_str()
       -        ctx = self.remote_commitment_to_be_revoked
       -        encumbered_sweeptx = maybe_create_sweeptx_for_their_ctx_to_local(self, ctx, per_commitment_secret, self.sweep_address)
       -        self.lnwatcher.add_sweep_tx(outpoint, ctx.txid(), encumbered_sweeptx)
       -
       -    def receive_revocation(self, revocation):
       -        """
       -        ReceiveRevocation processes a revocation sent by the remote party for the
       -        lowest unrevoked commitment within their commitment chain. We receive a
       -        revocation either during the initial session negotiation wherein revocation
       -        windows are extended, or in response to a state update that we initiate. If
       -        successful, then the remote commitment chain is advanced by a single
       -        commitment, and a log compaction is attempted.
       -
       -        Returns the forwarding package corresponding to the remote commitment height
       -        that was revoked.
       -        """
       -        self.print_error("receive_revocation")
       -
       -        cur_point = self.config[REMOTE].current_per_commitment_point
       -        derived_point = ecc.ECPrivkey(revocation.per_commitment_secret).get_public_key_bytes(compressed=True)
       -        if cur_point != derived_point:
       -            raise Exception('revoked secret not for current point')
       -
       -        # FIXME not sure this is correct... but it seems to work
       -        # if there are update_add_htlc msgs between commitment_signed and rev_ack,
       -        # this might break
       -        prev_remote_commitment = self.pending_remote_commitment
       -
       -        self.config[REMOTE].revocation_store.add_next_entry(revocation.per_commitment_secret)
       -        self.process_new_revocation_secret(revocation.per_commitment_secret)
       -
       -        def mark_settled(subject):
       -            """
       -            find pending settlements for subject (LOCAL or REMOTE) and mark them settled, return value of settled htlcs
       -            """
       -            old_amount = htlcsum(self.htlcs(subject, False))
       -
       -            for htlc_id in self.log[-subject]['settles']:
       -                adds = self.log[subject]['adds']
       -                htlc = adds.pop(htlc_id)
       -                self.settled[subject].append(htlc.amount_msat)
       -            self.log[-subject]['settles'].clear()
       -
       -            return old_amount - htlcsum(self.htlcs(subject, False))
       -
       -        sent_this_batch = mark_settled(LOCAL)
       -        received_this_batch = mark_settled(REMOTE)
       -
       -        next_point = self.config[REMOTE].next_per_commitment_point
       -
       -        print("RECEIVED", received_this_batch)
       -        print("SENT", sent_this_batch)
       -        self.config[REMOTE]=self.config[REMOTE]._replace(
       -            ctn=self.config[REMOTE].ctn + 1,
       -            current_per_commitment_point=next_point,
       -            next_per_commitment_point=revocation.next_per_commitment_point,
       -            amount_msat=self.config[REMOTE].amount_msat + (sent_this_batch - received_this_batch)
       -        )
       -        self.config[LOCAL]=self.config[LOCAL]._replace(
       -            amount_msat = self.config[LOCAL].amount_msat + (received_this_batch - sent_this_batch)
       -        )
       -
       -        for pending_fee in self.fee_mgr:
       -            if self.constraints.is_initiator:
       -                pending_fee[FUNDEE_ACKED] = True
       -
       -        self.local_commitment = self.pending_local_commitment
       -        self.remote_commitment = self.pending_remote_commitment
       -        self.remote_commitment_to_be_revoked = prev_remote_commitment
       -        return received_this_batch, sent_this_batch
       -
       -    def balance(self, subject):
       -        initial = self.config[subject].initial_msat
       -
       -        initial -= sum(self.settled[subject])
       -        initial += sum(self.settled[-subject])
       -
       -        assert initial == self.config[subject].amount_msat
       -        return initial
       -
       -    def amounts(self):
       -        remote_settled= htlcsum(self.htlcs(REMOTE, False))
       -        local_settled= htlcsum(self.htlcs(LOCAL, False))
       -        unsettled_local = htlcsum(self.htlcs(LOCAL, True))
       -        unsettled_remote = htlcsum(self.htlcs(REMOTE, True))
       -        remote_msat = self.config[REMOTE].amount_msat -\
       -          unsettled_remote + local_settled - remote_settled
       -        local_msat = self.config[LOCAL].amount_msat -\
       -          unsettled_local + remote_settled - local_settled
       -        return remote_msat, local_msat
       -
       -    def included_htlcs(self, subject, htlc_initiator):
       -        """
       -        return filter of non-dust htlcs for subjects commitment transaction, initiated by given party
       -        """
       -        feerate = self.pending_feerate(subject)
       -        conf = self.config[subject]
       -        weight = HTLC_SUCCESS_WEIGHT if subject != htlc_initiator else HTLC_TIMEOUT_WEIGHT
       -        htlcs = self.htlcs(htlc_initiator, only_pending=True)
       -        fee_for_htlc = lambda htlc: htlc.amount_msat // 1000 - (weight * feerate // 1000)
       -        return filter(lambda htlc: fee_for_htlc(htlc) >= conf.dust_limit_sat, htlcs)
       -
       -    @property
       -    def pending_remote_commitment(self):
       -        this_point = self.config[REMOTE].next_per_commitment_point
       -        return self.make_commitment(REMOTE, this_point)
       -
       -    def pending_feerate(self, subject):
       -        candidate = self.constraints.feerate
       -        for pending_fee in self.fee_mgr:
       -            x = pending_fee.pending_feerate(subject)
       -            if x is not None:
       -                candidate = x
       -
       -        return candidate
       -
       -    @property
       -    def pending_local_commitment(self):
       -        _, this_point, _ = self.points
       -        return self.make_commitment(LOCAL, this_point)
       -
       -    def total_msat(self, sub):
       -        return sum(self.settled[sub])
       -
       -    def htlcs(self, subject, only_pending):
       -        """
       -        only_pending: require the htlc's settlement to be pending (needs additional signatures/acks)
       -        """
       -        update_log = self.log[subject]
       -        other_log = self.log[-subject]
       -        res = []
       -        for htlc in update_log['adds'].values():
       -            locked_in = htlc.locked_in[subject]
       -
       -            if locked_in is None or only_pending == (htlc.htlc_id in other_log['settles']):
       -                continue
       -            res.append(htlc)
       -        return res
       -
       -    def settle_htlc(self, preimage, htlc_id):
       -        """
       -        SettleHTLC attempts to settle an existing outstanding received HTLC.
       -        """
       -        self.print_error("settle_htlc")
       -        htlc = self.log[REMOTE]['adds'][htlc_id]
       -        assert htlc.payment_hash == sha256(preimage)
       -        self.log[LOCAL]['settles'].append(htlc_id)
       -
       -    def receive_htlc_settle(self, preimage, htlc_index):
       -        self.print_error("receive_htlc_settle")
       -        htlc = self.log[LOCAL]['adds'][htlc_index]
       -        assert htlc.payment_hash == sha256(preimage)
       -        self.log[REMOTE]['settles'].append(htlc_index)
       -
       -    def receive_fail_htlc(self, htlc_id):
       -        self.print_error("receive_fail_htlc")
       -        self.log[LOCAL]['adds'].pop(htlc_id)
       -
       -    @property
       -    def current_height(self):
       -        return {LOCAL: self.config[LOCAL].ctn, REMOTE: self.config[REMOTE].ctn}
       -
       -    @property
       -    def pending_local_fee(self):
       -        return self.constraints.capacity - sum(x[2] for x in self.pending_local_commitment.outputs())
       -
       -    def update_fee(self, feerate):
       -        if not self.constraints.is_initiator:
       -            raise Exception("only initiator can update_fee, this counterparty is not initiator")
       -        pending_fee = FeeUpdate(self, rate=feerate)
       -        self.fee_mgr.append(pending_fee)
       -
       -    def receive_update_fee(self, feerate):
       -        if self.constraints.is_initiator:
       -            raise Exception("only the non-initiator can receive_update_fee, this counterparty is initiator")
       -        pending_fee = FeeUpdate(self, rate=feerate)
       -        self.fee_mgr.append(pending_fee)
       -
       -    def remove_uncommitted_htlcs_from_log(self, subject):
       -        """
       -        returns
       -        - the htlcs with uncommited (not locked in) htlcs removed
       -        - a list of htlc_ids that were removed
       -        """
       -        removed = []
       -        htlcs = []
       -        for i in self.log[subject]['adds'].values():
       -            locked_in = i.locked_in[LOCAL] is not None or i.locked_in[REMOTE] is not None
       -            if locked_in:
       -                htlcs.append(i._asdict())
       -            else:
       -                removed.append(i.htlc_id)
       -        return htlcs, removed
       -
       -    def to_save(self):
       -        # need to forget about uncommited htlcs
       -        # since we must assume they don't know about it,
       -        # if it was not acked
       -        remote_filtered, remote_removed = self.remove_uncommitted_htlcs_from_log(REMOTE)
       -        local_filtered, local_removed = self.remove_uncommitted_htlcs_from_log(LOCAL)
       -        to_save = {
       -                "local_config": self.config[LOCAL],
       -                "remote_config": self.config[REMOTE],
       -                "channel_id": self.channel_id,
       -                "short_channel_id": self.short_channel_id,
       -                "constraints": self.constraints,
       -                "funding_outpoint": self.funding_outpoint,
       -                "node_id": self.node_id,
       -                "remote_commitment_to_be_revoked": str(self.remote_commitment_to_be_revoked),
       -                "remote_log": remote_filtered,
       -                "local_log": local_filtered,
       -                "onion_keys": {str(k): bh2u(v) for k, v in self.onion_keys.items()},
       -                "settled_local": self.settled[LOCAL],
       -                "settled_remote": self.settled[REMOTE],
       -        }
       -
       -        # htlcs number must be monotonically increasing,
       -        # so we have to decrease the counter
       -        if len(remote_removed) != 0:
       -            assert min(remote_removed) < to_save['remote_config'].next_htlc_id
       -            to_save['remote_config'] = to_save['remote_config']._replace(next_htlc_id = min(remote_removed))
       -
       -        if len(local_removed) != 0:
       -            assert min(local_removed) < to_save['local_config'].next_htlc_id
       -            to_save['local_config'] = to_save['local_config']._replace(next_htlc_id = min(local_removed))
       -
       -        return to_save
       -
       -    def serialize(self):
       -        namedtuples_to_dict = lambda v: {i: j._asdict() if isinstance(j, tuple) else j for i, j in v._asdict().items()}
       -        serialized_channel = {k: namedtuples_to_dict(v) if isinstance(v, tuple) else v for k, v in self.to_save().items()}
       -        class MyJsonEncoder(json.JSONEncoder):
       -            def default(self, o):
       -                if isinstance(o, bytes):
       -                    return binascii.hexlify(o).decode("ascii")
       -                if isinstance(o, RevocationStore):
       -                    return o.serialize()
       -                return super(MyJsonEncoder, self)
       -        dumped = MyJsonEncoder().encode(serialized_channel)
       -        roundtripped = json.loads(dumped)
       -        reconstructed = HTLCStateMachine(roundtripped)
       -        if reconstructed.to_save() != self.to_save():
       -            from pprint import pformat
       -            try:
       -                from deepdiff import DeepDiff
       -            except ImportError:
       -                raise Exception("Channels did not roundtrip serialization without changes:\n" + pformat(reconstructed.to_save()) + "\n" + pformat(self.to_save()))
       -            else:
       -                raise Exception("Channels did not roundtrip serialization without changes:\n" + pformat(DeepDiff(reconstructed.to_save(), self.to_save())))
       -        return roundtripped
       -
       -    def __str__(self):
       -        return self.serialize()
       -
       -    def make_commitment(self, subject, this_point) -> Transaction:
       -        remote_msat, local_msat = self.amounts()
       -        assert local_msat >= 0
       -        assert remote_msat >= 0
       -        this_config = self.config[subject]
       -        other_config = self.config[-subject]
       -        other_htlc_pubkey = derive_pubkey(other_config.htlc_basepoint.pubkey, this_point)
       -        this_htlc_pubkey = derive_pubkey(this_config.htlc_basepoint.pubkey, this_point)
       -        other_revocation_pubkey = derive_blinded_pubkey(other_config.revocation_basepoint.pubkey, this_point)
       -        htlcs = []
       -        for htlc in self.included_htlcs(subject, -subject):
       -            htlcs.append( ScriptHtlc( make_received_htlc(
       -                other_revocation_pubkey,
       -                other_htlc_pubkey,
       -                this_htlc_pubkey,
       -                htlc.payment_hash,
       -                htlc.cltv_expiry), htlc))
       -        for htlc in self.included_htlcs(subject, subject):
       -            htlcs.append(
       -                ScriptHtlc( make_offered_htlc(
       -                    other_revocation_pubkey,
       -                    other_htlc_pubkey,
       -                    this_htlc_pubkey,
       -                    htlc.payment_hash), htlc))
       -        if subject != LOCAL:
       -            remote_msat, local_msat = local_msat, remote_msat
       -        payment_pubkey = derive_pubkey(other_config.payment_basepoint.pubkey, this_point)
       -        return make_commitment(
       -            self.config[subject].ctn + 1,
       -            this_config.multisig_key.pubkey,
       -            other_config.multisig_key.pubkey,
       -            payment_pubkey,
       -            self.config[LOCAL].payment_basepoint.pubkey,
       -            self.config[REMOTE].payment_basepoint.pubkey,
       -            other_revocation_pubkey,
       -            derive_pubkey(this_config.delayed_basepoint.pubkey, this_point),
       -            other_config.to_self_delay,
       -            *self.funding_outpoint,
       -            self.constraints.capacity,
       -            local_msat,
       -            remote_msat,
       -            this_config.dust_limit_sat,
       -            self.pending_feerate(subject),
       -            subject == LOCAL,
       -            self.constraints.is_initiator,
       -            htlcs=htlcs)
       -
       -    def make_closing_tx(self, local_script: bytes, remote_script: bytes, fee_sat: Optional[int] = None) -> (bytes, int):
       -        if fee_sat is None:
       -            fee_sat = self.pending_local_fee
       -
       -        _, outputs = make_outputs(fee_sat * 1000, True,
       -                self.config[LOCAL].amount_msat,
       -                self.config[REMOTE].amount_msat,
       -                (TYPE_SCRIPT, bh2u(local_script)),
       -                (TYPE_SCRIPT, bh2u(remote_script)),
       -                [], self.config[LOCAL].dust_limit_sat)
       -
       -        closing_tx = make_closing_tx(self.config[LOCAL].multisig_key.pubkey,
       -                self.config[REMOTE].multisig_key.pubkey,
       -                self.config[LOCAL].payment_basepoint.pubkey,
       -                self.config[REMOTE].payment_basepoint.pubkey,
       -                # TODO hardcoded we_are_initiator:
       -                True, *self.funding_outpoint, self.constraints.capacity,
       -                outputs)
       -
       -        der_sig = bfh(closing_tx.sign_txin(0, self.config[LOCAL].multisig_key.privkey))
       -        sig = ecc.sig_string_from_der_sig(der_sig[:-1])
       -        return sig, fee_sat
       -
       -def maybe_create_sweeptx_for_their_ctx_to_remote(chan, ctx, their_pcp: bytes,
       -                                                 sweep_address) -> Optional[EncumberedTransaction]:
       -    assert isinstance(their_pcp, bytes)
       -    payment_bp_privkey = ecc.ECPrivkey(chan.config[LOCAL].payment_basepoint.privkey)
       -    our_payment_privkey = derive_privkey(payment_bp_privkey.secret_scalar, their_pcp)
       -    our_payment_privkey = ecc.ECPrivkey.from_secret_scalar(our_payment_privkey)
       -    our_payment_pubkey = our_payment_privkey.get_public_key_bytes(compressed=True)
       -    to_remote_address = make_commitment_output_to_remote_address(our_payment_pubkey)
       -    for output_idx, (type_, addr, val) in enumerate(ctx.outputs()):
       -        if type_ == TYPE_ADDRESS and addr == to_remote_address:
       -            break
       -    else:
       -        return None
       -    sweep_tx = create_sweeptx_their_ctx_to_remote(address=sweep_address,
       -                                                  ctx=ctx,
       -                                                  output_idx=output_idx,
       -                                                  our_payment_privkey=our_payment_privkey)
       -    return EncumberedTransaction(sweep_tx, csv_delay=0)
       -
       -
       -def maybe_create_sweeptx_for_their_ctx_to_local(chan, ctx, per_commitment_secret: bytes,
       -                                                sweep_address) -> Optional[EncumberedTransaction]:
       -    assert isinstance(per_commitment_secret, bytes)
       -    per_commitment_point = ecc.ECPrivkey(per_commitment_secret).get_public_key_bytes(compressed=True)
       -    revocation_privkey = derive_blinded_privkey(chan.config[LOCAL].revocation_basepoint.privkey,
       -                                                per_commitment_secret)
       -    revocation_pubkey = ecc.ECPrivkey(revocation_privkey).get_public_key_bytes(compressed=True)
       -    to_self_delay = chan.config[LOCAL].to_self_delay
       -    delayed_pubkey = derive_pubkey(chan.config[REMOTE].delayed_basepoint.pubkey,
       -                                   per_commitment_point)
       -    witness_script = bh2u(make_commitment_output_to_local_witness_script(
       -        revocation_pubkey, to_self_delay, delayed_pubkey))
       -    to_local_address = redeem_script_to_address('p2wsh', witness_script)
       -    for output_idx, o in enumerate(ctx.outputs()):
       -        if o.type == TYPE_ADDRESS and o.address == to_local_address:
       -            break
       -    else:
       -        return None
       -    sweep_tx = create_sweeptx_ctx_to_local(address=sweep_address,
       -                                           ctx=ctx,
       -                                           output_idx=output_idx,
       -                                           witness_script=witness_script,
       -                                           privkey=revocation_privkey,
       -                                           is_revocation=True)
       -    return EncumberedTransaction(sweep_tx, csv_delay=0)
       -
       -
       -def maybe_create_sweeptx_for_our_ctx_to_local(chan, ctx, our_pcp: bytes,
       -                                              sweep_address) -> Optional[EncumberedTransaction]:
       -    assert isinstance(our_pcp, bytes)
       -    delayed_bp_privkey = ecc.ECPrivkey(chan.config[LOCAL].delayed_basepoint.privkey)
       -    our_localdelayed_privkey = derive_privkey(delayed_bp_privkey.secret_scalar, our_pcp)
       -    our_localdelayed_privkey = ecc.ECPrivkey.from_secret_scalar(our_localdelayed_privkey)
       -    our_localdelayed_pubkey = our_localdelayed_privkey.get_public_key_bytes(compressed=True)
       -    revocation_pubkey = derive_blinded_pubkey(chan.config[REMOTE].revocation_basepoint.pubkey,
       -                                              our_pcp)
       -    to_self_delay = chan.config[REMOTE].to_self_delay
       -    witness_script = bh2u(make_commitment_output_to_local_witness_script(
       -        revocation_pubkey, to_self_delay, our_localdelayed_pubkey))
       -    to_local_address = redeem_script_to_address('p2wsh', witness_script)
       -    for output_idx, o in enumerate(ctx.outputs()):
       -        if o.type == TYPE_ADDRESS and o.address == to_local_address:
       -            break
       -    else:
       -        return None
       -    sweep_tx = create_sweeptx_ctx_to_local(address=sweep_address,
       -                                           ctx=ctx,
       -                                           output_idx=output_idx,
       -                                           witness_script=witness_script,
       -                                           privkey=our_localdelayed_privkey.get_secret_bytes(),
       -                                           is_revocation=False,
       -                                           to_self_delay=to_self_delay)
       -
       -    return EncumberedTransaction(sweep_tx, csv_delay=to_self_delay)
       -
       -
       -def create_sweeptx_their_ctx_to_remote(address, ctx, output_idx: int, our_payment_privkey: ecc.ECPrivkey,
       -                                       fee_per_kb: int=None) -> Transaction:
       -    our_payment_pubkey = our_payment_privkey.get_public_key_hex(compressed=True)
       -    val = ctx.outputs()[output_idx].value
       -    sweep_inputs = [{
       -        'type': 'p2wpkh',
       -        'x_pubkeys': [our_payment_pubkey],
       -        'num_sig': 1,
       -        'prevout_n': output_idx,
       -        'prevout_hash': ctx.txid(),
       -        'value': val,
       -        'coinbase': False,
       -        'signatures': [None],
       -    }]
       -    tx_size_bytes = 110  # approx size of p2wpkh->p2wpkh
       -    if fee_per_kb is None: fee_per_kb = FEERATE_FALLBACK_STATIC_FEE
       -    fee = SimpleConfig.estimate_fee_for_feerate(fee_per_kb, tx_size_bytes)
       -    sweep_outputs = [TxOutput(TYPE_ADDRESS, address, val-fee)]
       -    sweep_tx = Transaction.from_io(sweep_inputs, sweep_outputs)
       -    sweep_tx.set_rbf(True)
       -    sweep_tx.sign({our_payment_pubkey: (our_payment_privkey.get_secret_bytes(), True)})
       -    if not sweep_tx.is_complete():
       -        raise Exception('channel close sweep tx is not complete')
       -    return sweep_tx
       -
       -
       -def create_sweeptx_ctx_to_local(address, ctx, output_idx: int, witness_script: str,
       -                                privkey: bytes, is_revocation: bool,
       -                                to_self_delay: int=None,
       -                                fee_per_kb: int=None) -> Transaction:
       -    """Create a txn that sweeps the 'to_local' output of a commitment
       -    transaction into our wallet.
       -
       -    privkey: either revocation_privkey or localdelayed_privkey
       -    is_revocation: tells us which ^
       -    """
       -    val = ctx.outputs()[output_idx].value
       -    sweep_inputs = [{
       -        'scriptSig': '',
       -        'type': 'p2wsh',
       -        'signatures': [],
       -        'num_sig': 0,
       -        'prevout_n': output_idx,
       -        'prevout_hash': ctx.txid(),
       -        'value': val,
       -        'coinbase': False,
       -        'preimage_script': witness_script,
       -    }]
       -    if to_self_delay is not None:
       -        sweep_inputs[0]['sequence'] = to_self_delay
       -    tx_size_bytes = 121  # approx size of to_local -> p2wpkh
       -    if fee_per_kb is None: fee_per_kb = FEERATE_FALLBACK_STATIC_FEE
       -    fee = SimpleConfig.estimate_fee_for_feerate(fee_per_kb, tx_size_bytes)
       -    sweep_outputs = [TxOutput(TYPE_ADDRESS, address, val - fee)]
       -    sweep_tx = Transaction.from_io(sweep_inputs, sweep_outputs, version=2)
       -    sig = sweep_tx.sign_txin(0, privkey)
       -    witness = construct_witness([sig, int(is_revocation), witness_script])
       -    sweep_tx.inputs()[0]['witness'] = witness
       -    return sweep_tx
   DIR diff --git a/electrum/lnworker.py b/electrum/lnworker.py
       t@@ -19,7 +19,7 @@ from .util import bh2u, bfh, PrintError, InvoiceError, resolve_dns_srv, is_ip_ad
        from .lnbase import Peer, aiosafe
        from .lnaddr import lnencode, LnAddr, lndecode
        from .ecc import der_sig_from_sig_string
       -from .lnhtlc import HTLCStateMachine
       +from .lnchan import Channel
        from .lnutil import (Outpoint, calc_short_channel_id, LNPeerAddr,
                             get_compressed_pubkey_from_bech32, extract_nodeid,
                             PaymentFailure, split_host_port, ConnStringFormatError,
       t@@ -50,7 +50,7 @@ class LNWorker(PrintError):
                self.node_keypair = generate_keypair(self.ln_keystore, LnKeyFamily.NODE_KEY, 0)
                self.config = network.config
                self.peers = {}  # type: Dict[bytes, Peer]  # pubkey -> Peer
       -        self.channels = {x.channel_id: x for x in map(HTLCStateMachine, wallet.storage.get("channels", []))}  # type: Dict[bytes, HTLCStateMachine]
       +        self.channels = {x.channel_id: x for x in map(Channel, wallet.storage.get("channels", []))}  # type: Dict[bytes, HTLCStateMachine]
                for c in self.channels.values():
                    c.lnwatcher = network.lnwatcher
                    c.sweep_address = self.sweep_address
       t@@ -115,7 +115,7 @@ class LNWorker(PrintError):
                return peer
        
            def save_channel(self, openchannel):
       -        assert type(openchannel) is HTLCStateMachine
       +        assert type(openchannel) is Channel
                if openchannel.config[REMOTE].next_per_commitment_point == openchannel.config[REMOTE].current_per_commitment_point:
                    raise Exception("Tried to save channel with next_point == current_point, this should not happen")
                with self.lock:
   DIR diff --git a/electrum/tests/test_lnchan.py b/electrum/tests/test_lnchan.py
       t@@ -0,0 +1,367 @@
       +# ported from lnd 42de4400bff5105352d0552155f73589166d162b
       +
       +import unittest
       +import electrum.bitcoin as bitcoin
       +import electrum.lnbase as lnbase
       +import electrum.lnchan as lnchan
       +import electrum.lnutil as lnutil
       +import electrum.util as util
       +import os
       +import binascii
       +
       +from electrum.lnutil import SENT, LOCAL, REMOTE, RECEIVED
       +
       +def create_channel_state(funding_txid, funding_index, funding_sat, local_feerate, is_initiator, local_amount, remote_amount, privkeys, other_pubkeys, seed, cur, nex, other_node_id, l_dust, r_dust, l_csv, r_csv):
       +    assert local_amount > 0
       +    assert remote_amount > 0
       +    channel_id, _ = lnbase.channel_id_from_funding_tx(funding_txid, funding_index)
       +    their_revocation_store = lnbase.RevocationStore()
       +
       +    return {
       +            "channel_id":channel_id,
       +            "short_channel_id":channel_id[:8],
       +            "funding_outpoint":lnbase.Outpoint(funding_txid, funding_index),
       +            "remote_config":lnbase.RemoteConfig(
       +                payment_basepoint=other_pubkeys[0],
       +                multisig_key=other_pubkeys[1],
       +                htlc_basepoint=other_pubkeys[2],
       +                delayed_basepoint=other_pubkeys[3],
       +                revocation_basepoint=other_pubkeys[4],
       +                to_self_delay=r_csv,
       +                dust_limit_sat=r_dust,
       +                max_htlc_value_in_flight_msat=500000 * 1000,
       +                max_accepted_htlcs=5,
       +                initial_msat=remote_amount,
       +                ctn = 0,
       +                next_htlc_id = 0,
       +                amount_msat=remote_amount,
       +
       +                next_per_commitment_point=nex,
       +                current_per_commitment_point=cur,
       +                revocation_store=their_revocation_store,
       +            ),
       +            "local_config":lnbase.LocalConfig(
       +                payment_basepoint=privkeys[0],
       +                multisig_key=privkeys[1],
       +                htlc_basepoint=privkeys[2],
       +                delayed_basepoint=privkeys[3],
       +                revocation_basepoint=privkeys[4],
       +                to_self_delay=l_csv,
       +                dust_limit_sat=l_dust,
       +                max_htlc_value_in_flight_msat=500000 * 1000,
       +                max_accepted_htlcs=5,
       +                initial_msat=local_amount,
       +                ctn = 0,
       +                next_htlc_id = 0,
       +                amount_msat=local_amount,
       +
       +                per_commitment_secret_seed=seed,
       +                funding_locked_received=True,
       +                was_announced=False,
       +                current_commitment_signature=None,
       +                current_htlc_signatures=None,
       +            ),
       +            "constraints":lnbase.ChannelConstraints(
       +                capacity=funding_sat,
       +                is_initiator=is_initiator,
       +                funding_txn_minimum_depth=3,
       +                feerate=local_feerate,
       +            ),
       +            "node_id":other_node_id,
       +            "remote_commitment_to_be_revoked": None,
       +            'onion_keys': {},
       +    }
       +
       +def bip32(sequence):
       +    xprv, xpub = bitcoin.bip32_root(b"9dk", 'standard')
       +    xprv, xpub = bitcoin.bip32_private_derivation(xprv, "m/", sequence)
       +    xtype, depth, fingerprint, child_number, c, k = bitcoin.deserialize_xprv(xprv)
       +    assert len(k) == 32
       +    assert type(k) is bytes
       +    return k
       +
       +def create_test_channels(feerate=6000, local=None, remote=None):
       +    funding_txid = binascii.hexlify(os.urandom(32)).decode("ascii")
       +    funding_index = 0
       +    funding_sat = ((local + remote) // 1000) if local is not None and remote is not None else (bitcoin.COIN * 10)
       +    local_amount = local if local is not None else (funding_sat * 1000 // 2)
       +    remote_amount = remote if remote is not None else (funding_sat * 1000 // 2)
       +    alice_raw = [ bip32("m/" + str(i)) for i in range(5) ]
       +    bob_raw = [ bip32("m/" + str(i)) for i in range(5,11) ]
       +    alice_privkeys = [lnutil.Keypair(lnbase.privkey_to_pubkey(x), x) for x in alice_raw]
       +    bob_privkeys = [lnutil.Keypair(lnbase.privkey_to_pubkey(x), x) for x in bob_raw]
       +    alice_pubkeys = [lnutil.OnlyPubkeyKeypair(x.pubkey) for x in alice_privkeys]
       +    bob_pubkeys = [lnutil.OnlyPubkeyKeypair(x.pubkey) for x in bob_privkeys]
       +
       +    alice_seed = os.urandom(32)
       +    bob_seed = os.urandom(32)
       +
       +    alice_cur = lnutil.secret_to_pubkey(int.from_bytes(lnutil.get_per_commitment_secret_from_seed(alice_seed, lnutil.RevocationStore.START_INDEX), "big"))
       +    alice_next = lnutil.secret_to_pubkey(int.from_bytes(lnutil.get_per_commitment_secret_from_seed(alice_seed, lnutil.RevocationStore.START_INDEX - 1), "big"))
       +    bob_cur = lnutil.secret_to_pubkey(int.from_bytes(lnutil.get_per_commitment_secret_from_seed(bob_seed, lnutil.RevocationStore.START_INDEX), "big"))
       +    bob_next = lnutil.secret_to_pubkey(int.from_bytes(lnutil.get_per_commitment_secret_from_seed(bob_seed, lnutil.RevocationStore.START_INDEX - 1), "big"))
       +
       +    return \
       +        lnchan.Channel(
       +            create_channel_state(funding_txid, funding_index, funding_sat, feerate, True, local_amount, remote_amount, alice_privkeys, bob_pubkeys, alice_seed, bob_cur, bob_next, b"\x02"*33, l_dust=200, r_dust=1300, l_csv=5, r_csv=4), "alice"), \
       +        lnchan.Channel(
       +            create_channel_state(funding_txid, funding_index, funding_sat, feerate, False, remote_amount, local_amount, bob_privkeys, alice_pubkeys, bob_seed, alice_cur, alice_next, b"\x01"*33, l_dust=1300, r_dust=200, l_csv=4, r_csv=5), "bob")
       +
       +one_bitcoin_in_msat = bitcoin.COIN * 1000
       +
       +class TestFee(unittest.TestCase):
       +    """
       +    test
       +    https://github.com/lightningnetwork/lightning-rfc/blob/e0c436bd7a3ed6a028e1cb472908224658a14eca/03-transactions.md#requirements-2
       +    """
       +    def test_SimpleAddSettleWorkflow(self):
       +        alice_channel, bob_channel = create_test_channels(253, 10000000000, 5000000000)
       +        self.assertIn(9999817, [x[2] for x in alice_channel.local_commitment.outputs()])
       +
       +class TestChannel(unittest.TestCase):
       +    def assertOutputExistsByValue(self, tx, amt_sat):
       +        for typ, scr, val in tx.outputs():
       +            if val == amt_sat:
       +                break
       +        else:
       +            self.assertFalse()
       +
       +    def setUp(self):
       +        # Create a test channel which will be used for the duration of this
       +        # unittest. The channel will be funded evenly with Alice having 5 BTC,
       +        # and Bob having 5 BTC.
       +        self.alice_channel, self.bob_channel = create_test_channels()
       +
       +        self.paymentPreimage = b"\x01" * 32
       +        paymentHash = bitcoin.sha256(self.paymentPreimage)
       +        self.htlc = {
       +            'payment_hash' : paymentHash,
       +            'amount_msat' :  one_bitcoin_in_msat,
       +            'cltv_expiry' :  5,
       +        }
       +
       +        # First Alice adds the outgoing HTLC to her local channel's state
       +        # update log. Then Alice sends this wire message over to Bob who adds
       +        # this htlc to his remote state update log.
       +        self.aliceHtlcIndex = self.alice_channel.add_htlc(self.htlc)
       +
       +        self.bobHtlcIndex = self.bob_channel.receive_htlc(self.htlc)
       +        self.htlc = self.bob_channel.log[lnutil.REMOTE]['adds'][0]
       +
       +    def test_SimpleAddSettleWorkflow(self):
       +        alice_channel, bob_channel = self.alice_channel, self.bob_channel
       +        htlc = self.htlc
       +
       +        # Next alice commits this change by sending a signature message. Since
       +        # we expect the messages to be ordered, Bob will receive the HTLC we
       +        # just sent before he receives this signature, so the signature will
       +        # cover the HTLC.
       +        aliceSig, aliceHtlcSigs = alice_channel.sign_next_commitment()
       +
       +        self.assertEqual(len(aliceHtlcSigs), 1, "alice should generate one htlc signature")
       +
       +        # Bob receives this signature message, and checks that this covers the
       +        # state he has in his remote log. This includes the HTLC just sent
       +        # from Alice.
       +        bob_channel.receive_new_commitment(aliceSig, aliceHtlcSigs)
       +
       +        # Bob revokes his prior commitment given to him by Alice, since he now
       +        # has a valid signature for a newer commitment.
       +        bobRevocation, _ = bob_channel.revoke_current_commitment()
       +
       +        # Bob finally send a signature for Alice's commitment transaction.
       +        # This signature will cover the HTLC, since Bob will first send the
       +        # revocation just created. The revocation also acks every received
       +        # HTLC up to the point where Alice sent here signature.
       +        bobSig, bobHtlcSigs = bob_channel.sign_next_commitment()
       +
       +        # Alice then processes this revocation, sending her own revocation for
       +        # her prior commitment transaction. Alice shouldn't have any HTLCs to
       +        # forward since she's sending an outgoing HTLC.
       +        alice_channel.receive_revocation(bobRevocation)
       +
       +        # Alice then processes bob's signature, and since she just received
       +        # the revocation, she expect this signature to cover everything up to
       +        # the point where she sent her signature, including the HTLC.
       +        alice_channel.receive_new_commitment(bobSig, bobHtlcSigs)
       +
       +        # Alice then generates a revocation for bob.
       +        aliceRevocation, _ = alice_channel.revoke_current_commitment()
       +
       +        # Finally Bob processes Alice's revocation, at this point the new HTLC
       +        # is fully locked in within both commitment transactions. Bob should
       +        # also be able to forward an HTLC now that the HTLC has been locked
       +        # into both commitment transactions.
       +        bob_channel.receive_revocation(aliceRevocation)
       +
       +        # At this point, both sides should have the proper number of satoshis
       +        # sent, and commitment height updated within their local channel
       +        # state.
       +        aliceSent = 0
       +        bobSent = 0
       +
       +        self.assertEqual(alice_channel.total_msat(SENT), aliceSent, "alice has incorrect milli-satoshis sent")
       +        self.assertEqual(alice_channel.total_msat(RECEIVED), bobSent, "alice has incorrect milli-satoshis received")
       +        self.assertEqual(bob_channel.total_msat(SENT), bobSent, "bob has incorrect milli-satoshis sent")
       +        self.assertEqual(bob_channel.total_msat(RECEIVED), aliceSent, "bob has incorrect milli-satoshis received")
       +        self.assertEqual(bob_channel.config[LOCAL].ctn, 1, "bob has incorrect commitment height")
       +        self.assertEqual(alice_channel.config[LOCAL].ctn, 1, "alice has incorrect commitment height")
       +
       +        # Both commitment transactions should have three outputs, and one of
       +        # them should be exactly the amount of the HTLC.
       +        self.assertEqual(len(alice_channel.local_commitment.outputs()), 3, "alice should have three commitment outputs, instead have %s"% len(alice_channel.local_commitment.outputs()))
       +        self.assertEqual(len(bob_channel.local_commitment.outputs()), 3, "bob should have three commitment outputs, instead have %s"% len(bob_channel.local_commitment.outputs()))
       +        self.assertOutputExistsByValue(alice_channel.local_commitment, htlc.amount_msat // 1000)
       +        self.assertOutputExistsByValue(bob_channel.local_commitment, htlc.amount_msat // 1000)
       +
       +        # Now we'll repeat a similar exchange, this time with Bob settling the
       +        # HTLC once he learns of the preimage.
       +        preimage = self.paymentPreimage
       +        bob_channel.settle_htlc(preimage, self.bobHtlcIndex)
       +
       +        alice_channel.receive_htlc_settle(preimage, self.aliceHtlcIndex)
       +
       +        bobSig2, bobHtlcSigs2 = bob_channel.sign_next_commitment()
       +        alice_channel.receive_new_commitment(bobSig2, bobHtlcSigs2)
       +
       +        aliceRevocation2, _ = alice_channel.revoke_current_commitment()
       +        aliceSig2, aliceHtlcSigs2 = alice_channel.sign_next_commitment()
       +        self.assertEqual(aliceHtlcSigs2, [], "alice should generate no htlc signatures")
       +
       +        received, sent = bob_channel.receive_revocation(aliceRevocation2)
       +        self.assertEqual(received, one_bitcoin_in_msat)
       +
       +        bob_channel.receive_new_commitment(aliceSig2, aliceHtlcSigs2)
       +
       +        bobRevocation2, _ = bob_channel.revoke_current_commitment()
       +        alice_channel.receive_revocation(bobRevocation2)
       +
       +        # At this point, Bob should have 6 BTC settled, with Alice still having
       +        # 4 BTC. Alice's channel should show 1 BTC sent and Bob's channel
       +        # should show 1 BTC received. They should also be at commitment height
       +        # two, with the revocation window extended by 1 (5).
       +        mSatTransferred = one_bitcoin_in_msat
       +        self.assertEqual(alice_channel.total_msat(SENT), mSatTransferred, "alice satoshis sent incorrect")
       +        self.assertEqual(alice_channel.total_msat(RECEIVED), 0, "alice satoshis received incorrect")
       +        self.assertEqual(bob_channel.total_msat(RECEIVED), mSatTransferred, "bob satoshis received incorrect")
       +        self.assertEqual(bob_channel.total_msat(SENT), 0, "bob satoshis sent incorrect")
       +        self.assertEqual(bob_channel.current_height[LOCAL], 2, "bob has incorrect commitment height")
       +        self.assertEqual(alice_channel.current_height[LOCAL], 2, "alice has incorrect commitment height")
       +
       +        # The logs of both sides should now be cleared since the entry adding
       +        # the HTLC should have been removed once both sides receive the
       +        # revocation.
       +        #self.assertEqual(alice_channel.local_update_log, [], "alice's local not updated, should be empty, has %s entries instead"% len(alice_channel.local_update_log))
       +        #self.assertEqual(alice_channel.remote_update_log, [], "alice's remote not updated, should be empty, has %s entries instead"% len(alice_channel.remote_update_log))
       +        alice_channel.update_fee(100000)
       +        alice_channel.serialize()
       +
       +    def alice_to_bob_fee_update(self):
       +        fee = 111
       +        self.alice_channel.update_fee(fee)
       +        self.bob_channel.receive_update_fee(fee)
       +        return fee
       +
       +    def test_UpdateFeeSenderCommits(self):
       +        old_feerate = self.alice_channel.pending_feerate(LOCAL)
       +        fee = self.alice_to_bob_fee_update()
       +
       +        alice_channel, bob_channel = self.alice_channel, self.bob_channel
       +
       +        self.assertEqual(self.alice_channel.pending_feerate(LOCAL), old_feerate)
       +        alice_sig, alice_htlc_sigs = alice_channel.sign_next_commitment()
       +        self.assertEqual(self.alice_channel.pending_feerate(LOCAL), old_feerate)
       +
       +        bob_channel.receive_new_commitment(alice_sig, alice_htlc_sigs)
       +
       +        self.assertNotEqual(fee, bob_channel.constraints.feerate)
       +        rev, _ = bob_channel.revoke_current_commitment()
       +        self.assertEqual(fee, bob_channel.constraints.feerate)
       +
       +        bob_sig, bob_htlc_sigs = bob_channel.sign_next_commitment()
       +        alice_channel.receive_revocation(rev)
       +        alice_channel.receive_new_commitment(bob_sig, bob_htlc_sigs)
       +
       +        self.assertNotEqual(fee, alice_channel.constraints.feerate)
       +        rev, _ = alice_channel.revoke_current_commitment()
       +        self.assertEqual(fee, alice_channel.constraints.feerate)
       +
       +        bob_channel.receive_revocation(rev)
       +        self.assertEqual(fee, bob_channel.constraints.feerate)
       +
       +
       +    def test_UpdateFeeReceiverCommits(self):
       +        fee = self.alice_to_bob_fee_update()
       +
       +        alice_channel, bob_channel = self.alice_channel, self.bob_channel
       +
       +        bob_sig, bob_htlc_sigs = bob_channel.sign_next_commitment()
       +        alice_channel.receive_new_commitment(bob_sig, bob_htlc_sigs)
       +
       +        alice_revocation, _ = alice_channel.revoke_current_commitment()
       +        bob_channel.receive_revocation(alice_revocation)
       +        alice_sig, alice_htlc_sigs = alice_channel.sign_next_commitment()
       +        bob_channel.receive_new_commitment(alice_sig, alice_htlc_sigs)
       +
       +        self.assertNotEqual(fee, bob_channel.constraints.feerate)
       +        bob_revocation, _ = bob_channel.revoke_current_commitment()
       +        self.assertEqual(fee, bob_channel.constraints.feerate)
       +
       +        bob_sig, bob_htlc_sigs = bob_channel.sign_next_commitment()
       +        alice_channel.receive_revocation(bob_revocation)
       +        alice_channel.receive_new_commitment(bob_sig, bob_htlc_sigs)
       +
       +        self.assertNotEqual(fee, alice_channel.constraints.feerate)
       +        alice_revocation, _ = alice_channel.revoke_current_commitment()
       +        self.assertEqual(fee, alice_channel.constraints.feerate)
       +
       +        bob_channel.receive_revocation(alice_revocation)
       +        self.assertEqual(fee, bob_channel.constraints.feerate)
       +
       +
       +
       +class TestDust(unittest.TestCase):
       +    def test_DustLimit(self):
       +        alice_channel, bob_channel = create_test_channels()
       +
       +        paymentPreimage = b"\x01" * 32
       +        paymentHash = bitcoin.sha256(paymentPreimage)
       +        fee_per_kw = alice_channel.constraints.feerate
       +        self.assertEqual(fee_per_kw, 6000)
       +        htlcAmt = 500 + lnutil.HTLC_TIMEOUT_WEIGHT * (fee_per_kw // 1000)
       +        self.assertEqual(htlcAmt, 4478)
       +        htlc = {
       +            'payment_hash' : paymentHash,
       +            'amount_msat' :  1000 * htlcAmt,
       +            'cltv_expiry' :  5, # also in create_test_channels
       +        }
       +
       +        aliceHtlcIndex = alice_channel.add_htlc(htlc)
       +        bobHtlcIndex = bob_channel.receive_htlc(htlc)
       +        force_state_transition(alice_channel, bob_channel)
       +        self.assertEqual(len(alice_channel.local_commitment.outputs()), 3)
       +        self.assertEqual(len(bob_channel.local_commitment.outputs()), 2)
       +        default_fee = calc_static_fee(0)
       +        self.assertEqual(bob_channel.pending_local_fee, default_fee + htlcAmt)
       +        bob_channel.settle_htlc(paymentPreimage, bobHtlcIndex)
       +        alice_channel.receive_htlc_settle(paymentPreimage, aliceHtlcIndex)
       +        force_state_transition(bob_channel, alice_channel)
       +        self.assertEqual(len(alice_channel.local_commitment.outputs()), 2)
       +        self.assertEqual(alice_channel.total_msat(SENT) // 1000, htlcAmt)
       +
       +def force_state_transition(chanA, chanB):
       +    chanB.receive_new_commitment(*chanA.sign_next_commitment())
       +    rev, _ = chanB.revoke_current_commitment()
       +    bob_sig, bob_htlc_sigs = chanB.sign_next_commitment()
       +    chanA.receive_revocation(rev)
       +    chanA.receive_new_commitment(bob_sig, bob_htlc_sigs)
       +    chanB.receive_revocation(chanA.revoke_current_commitment()[0])
       +
       +# calcStaticFee calculates appropriate fees for commitment transactions.  This
       +# function provides a simple way to allow test balance assertions to take fee
       +# calculations into account.
       +def calc_static_fee(numHTLCs):
       +    commitWeight = 724
       +    htlcWeight   = 172
       +    feePerKw     = 24//4 * 1000
       +    return feePerKw * (commitWeight + htlcWeight*numHTLCs) // 1000
   DIR diff --git a/electrum/tests/test_lnhtlc.py b/electrum/tests/test_lnhtlc.py
       t@@ -1,367 +0,0 @@
       -# ported from lnd 42de4400bff5105352d0552155f73589166d162b
       -
       -import unittest
       -import electrum.bitcoin as bitcoin
       -import electrum.lnbase as lnbase
       -import electrum.lnhtlc as lnhtlc
       -import electrum.lnutil as lnutil
       -import electrum.util as util
       -import os
       -import binascii
       -
       -from electrum.lnutil import SENT, LOCAL, REMOTE, RECEIVED
       -
       -def create_channel_state(funding_txid, funding_index, funding_sat, local_feerate, is_initiator, local_amount, remote_amount, privkeys, other_pubkeys, seed, cur, nex, other_node_id, l_dust, r_dust, l_csv, r_csv):
       -    assert local_amount > 0
       -    assert remote_amount > 0
       -    channel_id, _ = lnbase.channel_id_from_funding_tx(funding_txid, funding_index)
       -    their_revocation_store = lnbase.RevocationStore()
       -
       -    return {
       -            "channel_id":channel_id,
       -            "short_channel_id":channel_id[:8],
       -            "funding_outpoint":lnbase.Outpoint(funding_txid, funding_index),
       -            "remote_config":lnbase.RemoteConfig(
       -                payment_basepoint=other_pubkeys[0],
       -                multisig_key=other_pubkeys[1],
       -                htlc_basepoint=other_pubkeys[2],
       -                delayed_basepoint=other_pubkeys[3],
       -                revocation_basepoint=other_pubkeys[4],
       -                to_self_delay=r_csv,
       -                dust_limit_sat=r_dust,
       -                max_htlc_value_in_flight_msat=500000 * 1000,
       -                max_accepted_htlcs=5,
       -                initial_msat=remote_amount,
       -                ctn = 0,
       -                next_htlc_id = 0,
       -                amount_msat=remote_amount,
       -
       -                next_per_commitment_point=nex,
       -                current_per_commitment_point=cur,
       -                revocation_store=their_revocation_store,
       -            ),
       -            "local_config":lnbase.LocalConfig(
       -                payment_basepoint=privkeys[0],
       -                multisig_key=privkeys[1],
       -                htlc_basepoint=privkeys[2],
       -                delayed_basepoint=privkeys[3],
       -                revocation_basepoint=privkeys[4],
       -                to_self_delay=l_csv,
       -                dust_limit_sat=l_dust,
       -                max_htlc_value_in_flight_msat=500000 * 1000,
       -                max_accepted_htlcs=5,
       -                initial_msat=local_amount,
       -                ctn = 0,
       -                next_htlc_id = 0,
       -                amount_msat=local_amount,
       -
       -                per_commitment_secret_seed=seed,
       -                funding_locked_received=True,
       -                was_announced=False,
       -                current_commitment_signature=None,
       -                current_htlc_signatures=None,
       -            ),
       -            "constraints":lnbase.ChannelConstraints(
       -                capacity=funding_sat,
       -                is_initiator=is_initiator,
       -                funding_txn_minimum_depth=3,
       -                feerate=local_feerate,
       -            ),
       -            "node_id":other_node_id,
       -            "remote_commitment_to_be_revoked": None,
       -            'onion_keys': {},
       -    }
       -
       -def bip32(sequence):
       -    xprv, xpub = bitcoin.bip32_root(b"9dk", 'standard')
       -    xprv, xpub = bitcoin.bip32_private_derivation(xprv, "m/", sequence)
       -    xtype, depth, fingerprint, child_number, c, k = bitcoin.deserialize_xprv(xprv)
       -    assert len(k) == 32
       -    assert type(k) is bytes
       -    return k
       -
       -def create_test_channels(feerate=6000, local=None, remote=None):
       -    funding_txid = binascii.hexlify(os.urandom(32)).decode("ascii")
       -    funding_index = 0
       -    funding_sat = ((local + remote) // 1000) if local is not None and remote is not None else (bitcoin.COIN * 10)
       -    local_amount = local if local is not None else (funding_sat * 1000 // 2)
       -    remote_amount = remote if remote is not None else (funding_sat * 1000 // 2)
       -    alice_raw = [ bip32("m/" + str(i)) for i in range(5) ]
       -    bob_raw = [ bip32("m/" + str(i)) for i in range(5,11) ]
       -    alice_privkeys = [lnutil.Keypair(lnbase.privkey_to_pubkey(x), x) for x in alice_raw]
       -    bob_privkeys = [lnutil.Keypair(lnbase.privkey_to_pubkey(x), x) for x in bob_raw]
       -    alice_pubkeys = [lnutil.OnlyPubkeyKeypair(x.pubkey) for x in alice_privkeys]
       -    bob_pubkeys = [lnutil.OnlyPubkeyKeypair(x.pubkey) for x in bob_privkeys]
       -
       -    alice_seed = os.urandom(32)
       -    bob_seed = os.urandom(32)
       -
       -    alice_cur = lnutil.secret_to_pubkey(int.from_bytes(lnutil.get_per_commitment_secret_from_seed(alice_seed, lnutil.RevocationStore.START_INDEX), "big"))
       -    alice_next = lnutil.secret_to_pubkey(int.from_bytes(lnutil.get_per_commitment_secret_from_seed(alice_seed, lnutil.RevocationStore.START_INDEX - 1), "big"))
       -    bob_cur = lnutil.secret_to_pubkey(int.from_bytes(lnutil.get_per_commitment_secret_from_seed(bob_seed, lnutil.RevocationStore.START_INDEX), "big"))
       -    bob_next = lnutil.secret_to_pubkey(int.from_bytes(lnutil.get_per_commitment_secret_from_seed(bob_seed, lnutil.RevocationStore.START_INDEX - 1), "big"))
       -
       -    return \
       -        lnhtlc.HTLCStateMachine(
       -            create_channel_state(funding_txid, funding_index, funding_sat, feerate, True, local_amount, remote_amount, alice_privkeys, bob_pubkeys, alice_seed, bob_cur, bob_next, b"\x02"*33, l_dust=200, r_dust=1300, l_csv=5, r_csv=4), "alice"), \
       -        lnhtlc.HTLCStateMachine(
       -            create_channel_state(funding_txid, funding_index, funding_sat, feerate, False, remote_amount, local_amount, bob_privkeys, alice_pubkeys, bob_seed, alice_cur, alice_next, b"\x01"*33, l_dust=1300, r_dust=200, l_csv=4, r_csv=5), "bob")
       -
       -one_bitcoin_in_msat = bitcoin.COIN * 1000
       -
       -class TestFee(unittest.TestCase):
       -    """
       -    test
       -    https://github.com/lightningnetwork/lightning-rfc/blob/e0c436bd7a3ed6a028e1cb472908224658a14eca/03-transactions.md#requirements-2
       -    """
       -    def test_SimpleAddSettleWorkflow(self):
       -        alice_channel, bob_channel = create_test_channels(253, 10000000000, 5000000000)
       -        self.assertIn(9999817, [x[2] for x in alice_channel.local_commitment.outputs()])
       -
       -class TestLNBaseHTLCStateMachine(unittest.TestCase):
       -    def assertOutputExistsByValue(self, tx, amt_sat):
       -        for typ, scr, val in tx.outputs():
       -            if val == amt_sat:
       -                break
       -        else:
       -            self.assertFalse()
       -
       -    def setUp(self):
       -        # Create a test channel which will be used for the duration of this
       -        # unittest. The channel will be funded evenly with Alice having 5 BTC,
       -        # and Bob having 5 BTC.
       -        self.alice_channel, self.bob_channel = create_test_channels()
       -
       -        self.paymentPreimage = b"\x01" * 32
       -        paymentHash = bitcoin.sha256(self.paymentPreimage)
       -        self.htlc = {
       -            'payment_hash' : paymentHash,
       -            'amount_msat' :  one_bitcoin_in_msat,
       -            'cltv_expiry' :  5,
       -        }
       -
       -        # First Alice adds the outgoing HTLC to her local channel's state
       -        # update log. Then Alice sends this wire message over to Bob who adds
       -        # this htlc to his remote state update log.
       -        self.aliceHtlcIndex = self.alice_channel.add_htlc(self.htlc)
       -
       -        self.bobHtlcIndex = self.bob_channel.receive_htlc(self.htlc)
       -        self.htlc = self.bob_channel.log[lnutil.REMOTE]['adds'][0]
       -
       -    def test_SimpleAddSettleWorkflow(self):
       -        alice_channel, bob_channel = self.alice_channel, self.bob_channel
       -        htlc = self.htlc
       -
       -        # Next alice commits this change by sending a signature message. Since
       -        # we expect the messages to be ordered, Bob will receive the HTLC we
       -        # just sent before he receives this signature, so the signature will
       -        # cover the HTLC.
       -        aliceSig, aliceHtlcSigs = alice_channel.sign_next_commitment()
       -
       -        self.assertEqual(len(aliceHtlcSigs), 1, "alice should generate one htlc signature")
       -
       -        # Bob receives this signature message, and checks that this covers the
       -        # state he has in his remote log. This includes the HTLC just sent
       -        # from Alice.
       -        bob_channel.receive_new_commitment(aliceSig, aliceHtlcSigs)
       -
       -        # Bob revokes his prior commitment given to him by Alice, since he now
       -        # has a valid signature for a newer commitment.
       -        bobRevocation, _ = bob_channel.revoke_current_commitment()
       -
       -        # Bob finally send a signature for Alice's commitment transaction.
       -        # This signature will cover the HTLC, since Bob will first send the
       -        # revocation just created. The revocation also acks every received
       -        # HTLC up to the point where Alice sent here signature.
       -        bobSig, bobHtlcSigs = bob_channel.sign_next_commitment()
       -
       -        # Alice then processes this revocation, sending her own revocation for
       -        # her prior commitment transaction. Alice shouldn't have any HTLCs to
       -        # forward since she's sending an outgoing HTLC.
       -        alice_channel.receive_revocation(bobRevocation)
       -
       -        # Alice then processes bob's signature, and since she just received
       -        # the revocation, she expect this signature to cover everything up to
       -        # the point where she sent her signature, including the HTLC.
       -        alice_channel.receive_new_commitment(bobSig, bobHtlcSigs)
       -
       -        # Alice then generates a revocation for bob.
       -        aliceRevocation, _ = alice_channel.revoke_current_commitment()
       -
       -        # Finally Bob processes Alice's revocation, at this point the new HTLC
       -        # is fully locked in within both commitment transactions. Bob should
       -        # also be able to forward an HTLC now that the HTLC has been locked
       -        # into both commitment transactions.
       -        bob_channel.receive_revocation(aliceRevocation)
       -
       -        # At this point, both sides should have the proper number of satoshis
       -        # sent, and commitment height updated within their local channel
       -        # state.
       -        aliceSent = 0
       -        bobSent = 0
       -
       -        self.assertEqual(alice_channel.total_msat(SENT), aliceSent, "alice has incorrect milli-satoshis sent")
       -        self.assertEqual(alice_channel.total_msat(RECEIVED), bobSent, "alice has incorrect milli-satoshis received")
       -        self.assertEqual(bob_channel.total_msat(SENT), bobSent, "bob has incorrect milli-satoshis sent")
       -        self.assertEqual(bob_channel.total_msat(RECEIVED), aliceSent, "bob has incorrect milli-satoshis received")
       -        self.assertEqual(bob_channel.config[LOCAL].ctn, 1, "bob has incorrect commitment height")
       -        self.assertEqual(alice_channel.config[LOCAL].ctn, 1, "alice has incorrect commitment height")
       -
       -        # Both commitment transactions should have three outputs, and one of
       -        # them should be exactly the amount of the HTLC.
       -        self.assertEqual(len(alice_channel.local_commitment.outputs()), 3, "alice should have three commitment outputs, instead have %s"% len(alice_channel.local_commitment.outputs()))
       -        self.assertEqual(len(bob_channel.local_commitment.outputs()), 3, "bob should have three commitment outputs, instead have %s"% len(bob_channel.local_commitment.outputs()))
       -        self.assertOutputExistsByValue(alice_channel.local_commitment, htlc.amount_msat // 1000)
       -        self.assertOutputExistsByValue(bob_channel.local_commitment, htlc.amount_msat // 1000)
       -
       -        # Now we'll repeat a similar exchange, this time with Bob settling the
       -        # HTLC once he learns of the preimage.
       -        preimage = self.paymentPreimage
       -        bob_channel.settle_htlc(preimage, self.bobHtlcIndex)
       -
       -        alice_channel.receive_htlc_settle(preimage, self.aliceHtlcIndex)
       -
       -        bobSig2, bobHtlcSigs2 = bob_channel.sign_next_commitment()
       -        alice_channel.receive_new_commitment(bobSig2, bobHtlcSigs2)
       -
       -        aliceRevocation2, _ = alice_channel.revoke_current_commitment()
       -        aliceSig2, aliceHtlcSigs2 = alice_channel.sign_next_commitment()
       -        self.assertEqual(aliceHtlcSigs2, [], "alice should generate no htlc signatures")
       -
       -        received, sent = bob_channel.receive_revocation(aliceRevocation2)
       -        self.assertEqual(received, one_bitcoin_in_msat)
       -
       -        bob_channel.receive_new_commitment(aliceSig2, aliceHtlcSigs2)
       -
       -        bobRevocation2, _ = bob_channel.revoke_current_commitment()
       -        alice_channel.receive_revocation(bobRevocation2)
       -
       -        # At this point, Bob should have 6 BTC settled, with Alice still having
       -        # 4 BTC. Alice's channel should show 1 BTC sent and Bob's channel
       -        # should show 1 BTC received. They should also be at commitment height
       -        # two, with the revocation window extended by 1 (5).
       -        mSatTransferred = one_bitcoin_in_msat
       -        self.assertEqual(alice_channel.total_msat(SENT), mSatTransferred, "alice satoshis sent incorrect")
       -        self.assertEqual(alice_channel.total_msat(RECEIVED), 0, "alice satoshis received incorrect")
       -        self.assertEqual(bob_channel.total_msat(RECEIVED), mSatTransferred, "bob satoshis received incorrect")
       -        self.assertEqual(bob_channel.total_msat(SENT), 0, "bob satoshis sent incorrect")
       -        self.assertEqual(bob_channel.current_height[LOCAL], 2, "bob has incorrect commitment height")
       -        self.assertEqual(alice_channel.current_height[LOCAL], 2, "alice has incorrect commitment height")
       -
       -        # The logs of both sides should now be cleared since the entry adding
       -        # the HTLC should have been removed once both sides receive the
       -        # revocation.
       -        #self.assertEqual(alice_channel.local_update_log, [], "alice's local not updated, should be empty, has %s entries instead"% len(alice_channel.local_update_log))
       -        #self.assertEqual(alice_channel.remote_update_log, [], "alice's remote not updated, should be empty, has %s entries instead"% len(alice_channel.remote_update_log))
       -        alice_channel.update_fee(100000)
       -        alice_channel.serialize()
       -
       -    def alice_to_bob_fee_update(self):
       -        fee = 111
       -        self.alice_channel.update_fee(fee)
       -        self.bob_channel.receive_update_fee(fee)
       -        return fee
       -
       -    def test_UpdateFeeSenderCommits(self):
       -        old_feerate = self.alice_channel.pending_feerate(LOCAL)
       -        fee = self.alice_to_bob_fee_update()
       -
       -        alice_channel, bob_channel = self.alice_channel, self.bob_channel
       -
       -        self.assertEqual(self.alice_channel.pending_feerate(LOCAL), old_feerate)
       -        alice_sig, alice_htlc_sigs = alice_channel.sign_next_commitment()
       -        self.assertEqual(self.alice_channel.pending_feerate(LOCAL), old_feerate)
       -
       -        bob_channel.receive_new_commitment(alice_sig, alice_htlc_sigs)
       -
       -        self.assertNotEqual(fee, bob_channel.constraints.feerate)
       -        rev, _ = bob_channel.revoke_current_commitment()
       -        self.assertEqual(fee, bob_channel.constraints.feerate)
       -
       -        bob_sig, bob_htlc_sigs = bob_channel.sign_next_commitment()
       -        alice_channel.receive_revocation(rev)
       -        alice_channel.receive_new_commitment(bob_sig, bob_htlc_sigs)
       -
       -        self.assertNotEqual(fee, alice_channel.constraints.feerate)
       -        rev, _ = alice_channel.revoke_current_commitment()
       -        self.assertEqual(fee, alice_channel.constraints.feerate)
       -
       -        bob_channel.receive_revocation(rev)
       -        self.assertEqual(fee, bob_channel.constraints.feerate)
       -
       -
       -    def test_UpdateFeeReceiverCommits(self):
       -        fee = self.alice_to_bob_fee_update()
       -
       -        alice_channel, bob_channel = self.alice_channel, self.bob_channel
       -
       -        bob_sig, bob_htlc_sigs = bob_channel.sign_next_commitment()
       -        alice_channel.receive_new_commitment(bob_sig, bob_htlc_sigs)
       -
       -        alice_revocation, _ = alice_channel.revoke_current_commitment()
       -        bob_channel.receive_revocation(alice_revocation)
       -        alice_sig, alice_htlc_sigs = alice_channel.sign_next_commitment()
       -        bob_channel.receive_new_commitment(alice_sig, alice_htlc_sigs)
       -
       -        self.assertNotEqual(fee, bob_channel.constraints.feerate)
       -        bob_revocation, _ = bob_channel.revoke_current_commitment()
       -        self.assertEqual(fee, bob_channel.constraints.feerate)
       -
       -        bob_sig, bob_htlc_sigs = bob_channel.sign_next_commitment()
       -        alice_channel.receive_revocation(bob_revocation)
       -        alice_channel.receive_new_commitment(bob_sig, bob_htlc_sigs)
       -
       -        self.assertNotEqual(fee, alice_channel.constraints.feerate)
       -        alice_revocation, _ = alice_channel.revoke_current_commitment()
       -        self.assertEqual(fee, alice_channel.constraints.feerate)
       -
       -        bob_channel.receive_revocation(alice_revocation)
       -        self.assertEqual(fee, bob_channel.constraints.feerate)
       -
       -
       -
       -class TestLNHTLCDust(unittest.TestCase):
       -    def test_HTLCDustLimit(self):
       -        alice_channel, bob_channel = create_test_channels()
       -
       -        paymentPreimage = b"\x01" * 32
       -        paymentHash = bitcoin.sha256(paymentPreimage)
       -        fee_per_kw = alice_channel.constraints.feerate
       -        self.assertEqual(fee_per_kw, 6000)
       -        htlcAmt = 500 + lnutil.HTLC_TIMEOUT_WEIGHT * (fee_per_kw // 1000)
       -        self.assertEqual(htlcAmt, 4478)
       -        htlc = {
       -            'payment_hash' : paymentHash,
       -            'amount_msat' :  1000 * htlcAmt,
       -            'cltv_expiry' :  5, # also in create_test_channels
       -        }
       -
       -        aliceHtlcIndex = alice_channel.add_htlc(htlc)
       -        bobHtlcIndex = bob_channel.receive_htlc(htlc)
       -        force_state_transition(alice_channel, bob_channel)
       -        self.assertEqual(len(alice_channel.local_commitment.outputs()), 3)
       -        self.assertEqual(len(bob_channel.local_commitment.outputs()), 2)
       -        default_fee = calc_static_fee(0)
       -        self.assertEqual(bob_channel.pending_local_fee, default_fee + htlcAmt)
       -        bob_channel.settle_htlc(paymentPreimage, bobHtlcIndex)
       -        alice_channel.receive_htlc_settle(paymentPreimage, aliceHtlcIndex)
       -        force_state_transition(bob_channel, alice_channel)
       -        self.assertEqual(len(alice_channel.local_commitment.outputs()), 2)
       -        self.assertEqual(alice_channel.total_msat(SENT) // 1000, htlcAmt)
       -
       -def force_state_transition(chanA, chanB):
       -    chanB.receive_new_commitment(*chanA.sign_next_commitment())
       -    rev, _ = chanB.revoke_current_commitment()
       -    bob_sig, bob_htlc_sigs = chanB.sign_next_commitment()
       -    chanA.receive_revocation(rev)
       -    chanA.receive_new_commitment(bob_sig, bob_htlc_sigs)
       -    chanB.receive_revocation(chanA.revoke_current_commitment()[0])
       -
       -# calcStaticFee calculates appropriate fees for commitment transactions.  This
       -# function provides a simple way to allow test balance assertions to take fee
       -# calculations into account.
       -def calc_static_fee(numHTLCs):
       -    commitWeight = 724
       -    htlcWeight   = 172
       -    feePerKw     = 24//4 * 1000
       -    return feePerKw * (commitWeight + htlcWeight*numHTLCs) // 1000
   DIR diff --git a/electrum/tests/test_lnutil.py b/electrum/tests/test_lnutil.py
       t@@ -7,7 +7,7 @@ from electrum.lnutil import (RevocationStore, get_per_commitment_secret_from_see
                                     derive_pubkey, make_htlc_tx, extract_ctn_from_tx, UnableToDeriveSecret,
                                     get_compressed_pubkey_from_bech32, split_host_port, ConnStringFormatError,
                                     ScriptHtlc, extract_nodeid)
       -from electrum import lnhtlc
       +from electrum import lnchan
        from electrum.util import bh2u, bfh
        from electrum.transaction import Transaction
        
       t@@ -496,7 +496,7 @@ class TestLNUtil(unittest.TestCase):
                    (1, 2000 * 1000),
                    (3, 3000 * 1000),
                    (4, 4000 * 1000)]:
       -            htlc_obj[num] = lnhtlc.UpdateAddHtlc(amount_msat=msat, payment_hash=bitcoin.sha256(htlc_payment_preimage[num]), cltv_expiry=None, htlc_id=None)
       +            htlc_obj[num] = lnchan.UpdateAddHtlc(amount_msat=msat, payment_hash=bitcoin.sha256(htlc_payment_preimage[num]), cltv_expiry=None, htlc_id=None)
                htlcs = [ScriptHtlc(htlc[x], htlc_obj[x]) for x in range(5)]
        
                our_commit_tx = make_commitment(