URI: 
       tRename lnchan, lnchannel_verifier, lnbase Auto-completions are a pain if files share a long prefix - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
   DIR commit 3dce65dc7390f5ec30365b14b93eca55d1a30564
   DIR parent 8274a963e6e5c9ce11af198f6ca23d21b1c5ba41
  HTML Author: ThomasV <thomasv@electrum.org>
       Date:   Sat,  9 Feb 2019 10:29:33 +0100
       
       Rename lnchan, lnchannel_verifier, lnbase
       Auto-completions are a pain if files share a long prefix
       
       Diffstat:
         M electrum/commands.py                |       2 +-
         M electrum/gui/qt/channel_details.py  |       2 +-
         M electrum/gui/qt/channels_list.py    |       2 +-
         D electrum/lnbase.py                  |    1105 -------------------------------
         D electrum/lnchan.py                  |     810 -------------------------------
         A electrum/lnchannel.py               |     810 +++++++++++++++++++++++++++++++
         D electrum/lnchannelverifier.py       |     206 -------------------------------
         A electrum/lnpeer.py                  |    1105 +++++++++++++++++++++++++++++++
         M electrum/lnrouter.py                |       4 ++--
         M electrum/lnsweep.py                 |       4 ++--
         M electrum/lnutil.py                  |       4 ++--
         A electrum/lnverifier.py              |     205 ++++++++++++++++++++++++++++++
         M electrum/lnworker.py                |       4 ++--
         D electrum/tests/test_lnbase.py       |     252 -------------------------------
         D electrum/tests/test_lnchan.py       |     826 -------------------------------
         A electrum/tests/test_lnchannel.py    |     826 +++++++++++++++++++++++++++++++
         A electrum/tests/test_lnpeer.py       |     252 +++++++++++++++++++++++++++++++
       
       17 files changed, 3209 insertions(+), 3210 deletions(-)
       ---
   DIR diff --git a/electrum/commands.py b/electrum/commands.py
       t@@ -49,7 +49,7 @@ from .address_synchronizer import TX_HEIGHT_LOCAL
        from .import lightning
        from .mnemonic import Mnemonic
        from .lnutil import SENT, RECEIVED
       -from .lnbase import channel_id_from_funding_tx
       +from .lnpeer import channel_id_from_funding_tx
        
        if TYPE_CHECKING:
            from .network import Network
   DIR diff --git a/electrum/gui/qt/channel_details.py b/electrum/gui/qt/channel_details.py
       t@@ -7,7 +7,7 @@ import PyQt5.QtCore as QtCore
        from electrum.i18n import _
        from electrum.util import bh2u, format_time
        from electrum.lnutil import format_short_channel_id, LOCAL, REMOTE, UpdateAddHtlc, Direction
       -from electrum.lnchan import htlcsum
       +from electrum.lnchannel import htlcsum
        from electrum.lnaddr import LnAddr, lndecode
        from electrum.bitcoin import COIN
        
   DIR diff --git a/electrum/gui/qt/channels_list.py b/electrum/gui/qt/channels_list.py
       t@@ -6,7 +6,7 @@ from PyQt5.QtWidgets import *
        
        from electrum.util import inv_dict, bh2u, bfh
        from electrum.i18n import _
       -from electrum.lnchan import Channel
       +from electrum.lnchannel import Channel
        from electrum.lnutil import LOCAL, REMOTE, ConnStringFormatError
        
        from .util import MyTreeView, WindowModalDialog, Buttons, OkButton, CancelButton, EnterButton, WWLabel
   DIR diff --git a/electrum/lnbase.py b/electrum/lnbase.py
       t@@ -1,1105 +0,0 @@
       -#!/usr/bin/env python3
       -#
       -# Copyright (C) 2018 The Electrum developers
       -# Distributed under the MIT software license, see the accompanying
       -# file LICENCE or http://www.opensource.org/licenses/mit-license.php
       -
       -from collections import OrderedDict, defaultdict
       -import json
       -import asyncio
       -import os
       -import time
       -from functools import partial
       -from typing import List, Tuple, Dict, TYPE_CHECKING, Optional, Callable
       -import traceback
       -import sys
       -
       -import aiorpcx
       -
       -from .simple_config import get_config
       -from .crypto import sha256, sha256d
       -from . import bitcoin
       -from . import ecc
       -from .ecc import sig_string_from_r_and_s, get_r_and_s_from_sig_string, der_sig_from_sig_string
       -from . import constants
       -from .util import PrintError, bh2u, print_error, bfh, log_exceptions, list_enabled_bits, ignore_exceptions
       -from .transaction import Transaction, TxOutput
       -from .lnonion import (new_onion_packet, decode_onion_error, OnionFailureCode, calc_hops_data_for_payment,
       -                      process_onion_packet, OnionPacket, construct_onion_error, OnionRoutingFailureMessage)
       -from .lnchan import Channel, RevokeAndAck, htlcsum
       -from .lnutil import (Outpoint, LocalConfig, RECEIVED, UpdateAddHtlc,
       -                     RemoteConfig, OnlyPubkeyKeypair, ChannelConstraints, RevocationStore,
       -                     funding_output_script, get_per_commitment_secret_from_seed,
       -                     secret_to_pubkey, PaymentFailure, LnLocalFeatures,
       -                     LOCAL, REMOTE, HTLCOwner, generate_keypair, LnKeyFamily,
       -                     get_ln_flag_pair_of_bit, privkey_to_pubkey, UnknownPaymentHash, MIN_FINAL_CLTV_EXPIRY_ACCEPTED,
       -                     LightningPeerConnectionClosed, HandshakeFailed, NotFoundChanAnnouncementForUpdate,
       -                     MINIMUM_MAX_HTLC_VALUE_IN_FLIGHT_ACCEPTED, MAXIMUM_HTLC_MINIMUM_MSAT_ACCEPTED,
       -                     MAXIMUM_REMOTE_TO_SELF_DELAY_ACCEPTED)
       -from .lntransport import LNTransport, LNTransportBase
       -from .lnmsg import encode_msg, decode_msg
       -
       -if TYPE_CHECKING:
       -    from .lnworker import LNWorker
       -    from .lnrouter import RouteEdge
       -
       -
       -def channel_id_from_funding_tx(funding_txid: str, funding_index: int) -> Tuple[bytes, bytes]:
       -    funding_txid_bytes = bytes.fromhex(funding_txid)[::-1]
       -    i = int.from_bytes(funding_txid_bytes, 'big') ^ funding_index
       -    return i.to_bytes(32, 'big'), funding_txid_bytes
       -
       -class Peer(PrintError):
       -
       -    def __init__(self, lnworker: 'LNWorker', pubkey:bytes, transport: LNTransportBase,
       -                 request_initial_sync=False):
       -        self.initialized = asyncio.Event()
       -        self.transport = transport
       -        self.pubkey = pubkey
       -        self.lnworker = lnworker
       -        self.privkey = lnworker.node_keypair.privkey
       -        self.node_ids = [self.pubkey, privkey_to_pubkey(self.privkey)]
       -        self.network = lnworker.network
       -        self.lnwatcher = lnworker.network.lnwatcher
       -        self.channel_db = lnworker.network.channel_db
       -        self.ping_time = 0
       -        self.shutdown_received = defaultdict(asyncio.Future)
       -        self.channel_accepted = defaultdict(asyncio.Queue)
       -        self.channel_reestablished = defaultdict(asyncio.Future)
       -        self.funding_signed = defaultdict(asyncio.Queue)
       -        self.funding_created = defaultdict(asyncio.Queue)
       -        self.revoke_and_ack = defaultdict(asyncio.Queue)
       -        self.commitment_signed = defaultdict(asyncio.Queue)
       -        self.announcement_signatures = defaultdict(asyncio.Queue)
       -        self.closing_signed = defaultdict(asyncio.Queue)
       -        self.payment_preimages = defaultdict(asyncio.Queue)
       -        self.localfeatures = LnLocalFeatures(0)
       -        if request_initial_sync:
       -            self.localfeatures |= LnLocalFeatures.INITIAL_ROUTING_SYNC
       -        self.localfeatures |= LnLocalFeatures.OPTION_DATA_LOSS_PROTECT_REQ
       -        self.attempted_route = {}
       -        self.orphan_channel_updates = OrderedDict()
       -
       -    def send_message(self, message_name: str, **kwargs):
       -        assert type(message_name) is str
       -        self.print_error("Sending '%s'"%message_name.upper())
       -        self.transport.send_bytes(encode_msg(message_name, **kwargs))
       -
       -    async def initialize(self):
       -        if isinstance(self.transport, LNTransport):
       -            await self.transport.handshake()
       -            self.channel_db.add_recent_peer(self.transport.peer_addr)
       -        self.send_message("init", gflen=0, lflen=1, localfeatures=self.localfeatures)
       -
       -    @property
       -    def channels(self) -> Dict[bytes, Channel]:
       -        return self.lnworker.channels_for_peer(self.pubkey)
       -
       -    def diagnostic_name(self):
       -        return self.transport.name()
       -
       -    def ping_if_required(self):
       -        if time.time() - self.ping_time > 120:
       -            self.send_message('ping', num_pong_bytes=4, byteslen=4)
       -            self.ping_time = time.time()
       -
       -    def process_message(self, message):
       -        message_type, payload = decode_msg(message)
       -        try:
       -            f = getattr(self, 'on_' + message_type)
       -        except AttributeError:
       -            self.print_error("Received '%s'" % message_type.upper(), payload)
       -            return
       -        # raw message is needed to check signature
       -        if message_type=='node_announcement':
       -            payload['raw'] = message
       -        execution_result = f(payload)
       -        if asyncio.iscoroutinefunction(f):
       -            asyncio.ensure_future(execution_result)
       -
       -    def on_error(self, payload):
       -        # todo: self.channel_reestablished is not a queue
       -        self.print_error("error", payload["data"].decode("ascii"))
       -        chan_id = payload.get("channel_id")
       -        for d in [ self.channel_accepted, self.funding_signed,
       -                   self.funding_created, self.revoke_and_ack, self.commitment_signed,
       -                   self.announcement_signatures, self.closing_signed ]:
       -            if chan_id in d:
       -                d[chan_id].put_nowait({'error':payload['data']})
       -
       -    def on_ping(self, payload):
       -        l = int.from_bytes(payload['num_pong_bytes'], 'big')
       -        self.send_message('pong', byteslen=l)
       -
       -    def on_pong(self, payload):
       -        pass
       -
       -    def on_accept_channel(self, payload):
       -        temp_chan_id = payload["temporary_channel_id"]
       -        if temp_chan_id not in self.channel_accepted: raise Exception("Got unknown accept_channel")
       -        self.channel_accepted[temp_chan_id].put_nowait(payload)
       -
       -    def on_funding_signed(self, payload):
       -        channel_id = payload['channel_id']
       -        if channel_id not in self.funding_signed: raise Exception("Got unknown funding_signed")
       -        self.funding_signed[channel_id].put_nowait(payload)
       -
       -    def on_funding_created(self, payload):
       -        channel_id = payload['temporary_channel_id']
       -        if channel_id not in self.funding_created: raise Exception("Got unknown funding_created")
       -        self.funding_created[channel_id].put_nowait(payload)
       -
       -    def on_node_announcement(self, payload):
       -        self.channel_db.on_node_announcement(payload)
       -        self.network.trigger_callback('ln_status')
       -
       -    def on_init(self, payload):
       -        if self.initialized.is_set():
       -            self.print_error("ALREADY INITIALIZED BUT RECEIVED INIT")
       -            return
       -        # if they required some even flag we don't have, they will close themselves
       -        # but if we require an even flag they don't have, we close
       -        our_flags = set(list_enabled_bits(self.localfeatures))
       -        their_flags = set(list_enabled_bits(int.from_bytes(payload['localfeatures'], byteorder="big")))
       -        for flag in our_flags:
       -            if flag not in their_flags and get_ln_flag_pair_of_bit(flag) not in their_flags:
       -                # they don't have this feature we wanted :(
       -                if flag % 2 == 0:  # even flags are compulsory
       -                    raise LightningPeerConnectionClosed("remote does not have even flag {}"
       -                                                        .format(str(LnLocalFeatures(1 << flag))))
       -                self.localfeatures ^= 1 << flag  # disable flag
       -        first_timestamp = self.lnworker.get_first_timestamp()
       -        self.send_message('gossip_timestamp_filter', chain_hash=constants.net.rev_genesis_bytes(), first_timestamp=first_timestamp, timestamp_range=b"\xff"*4)
       -        self.initialized.set()
       -
       -    def on_channel_update(self, payload):
       -        try:
       -            self.channel_db.on_channel_update(payload)
       -        except NotFoundChanAnnouncementForUpdate:
       -            # If it's for a direct channel with this peer, save it for later, as it might be
       -            # for our own channel (and we might not yet know the short channel id for that)
       -            short_channel_id = payload['short_channel_id']
       -            self.print_error("not found channel announce for channel update in db", bh2u(short_channel_id))
       -            self.orphan_channel_updates[short_channel_id] = payload
       -            while len(self.orphan_channel_updates) > 10:
       -                self.orphan_channel_updates.popitem(last=False)
       -
       -    def on_channel_announcement(self, payload):
       -        self.channel_db.on_channel_announcement(payload)
       -
       -    def on_announcement_signatures(self, payload):
       -        channel_id = payload['channel_id']
       -        chan = self.channels[payload['channel_id']]
       -        if chan.config[LOCAL].was_announced:
       -            h, local_node_sig, local_bitcoin_sig = self.send_announcement_signatures(chan)
       -        else:
       -            self.announcement_signatures[channel_id].put_nowait(payload)
       -
       -    def handle_disconnect(func):
       -        async def wrapper_func(self, *args, **kwargs):
       -            try:
       -                return await func(self, *args, **kwargs)
       -            except LightningPeerConnectionClosed as e:
       -                self.print_error("disconnecting gracefully. {}".format(e))
       -            finally:
       -                self.close_and_cleanup()
       -                self.lnworker.peers.pop(self.pubkey)
       -        return wrapper_func
       -
       -    @ignore_exceptions  # do not kill main_taskgroup
       -    @log_exceptions
       -    @handle_disconnect
       -    async def main_loop(self):
       -        """
       -        This is used in LNWorker and is necessary so that we don't kill the main
       -        task group. It is not merged with _main_loop, so that we can test if the
       -        correct exceptions are getting thrown using _main_loop.
       -        """
       -        await self._main_loop()
       -
       -    async def _main_loop(self):
       -        """This is separate from main_loop for the tests."""
       -        try:
       -            await asyncio.wait_for(self.initialize(), 10)
       -        except (OSError, asyncio.TimeoutError, HandshakeFailed) as e:
       -            self.print_error('initialize failed, disconnecting: {}'.format(repr(e)))
       -            return
       -        # loop
       -        async for msg in self.transport.read_messages():
       -            self.process_message(msg)
       -            self.ping_if_required()
       -
       -    def close_and_cleanup(self):
       -        try:
       -            if self.transport:
       -                self.transport.close()
       -        except:
       -            pass
       -        for chan in self.channels.values():
       -            if chan.get_state() != 'FORCE_CLOSING':
       -                chan.set_state('DISCONNECTED')
       -            self.network.trigger_callback('channel', chan)
       -
       -    def make_local_config(self, funding_sat: int, push_msat: int, initiator: HTLCOwner) -> LocalConfig:
       -        # key derivation
       -        channel_counter = self.lnworker.get_and_inc_counter_for_channel_keys()
       -        keypair_generator = lambda family: generate_keypair(self.lnworker.ln_keystore, family, channel_counter)
       -        if initiator == LOCAL:
       -            initial_msat = funding_sat * 1000 - push_msat
       -        else:
       -            initial_msat = push_msat
       -        local_config=LocalConfig(
       -            payment_basepoint=keypair_generator(LnKeyFamily.PAYMENT_BASE),
       -            multisig_key=keypair_generator(LnKeyFamily.MULTISIG),
       -            htlc_basepoint=keypair_generator(LnKeyFamily.HTLC_BASE),
       -            delayed_basepoint=keypair_generator(LnKeyFamily.DELAY_BASE),
       -            revocation_basepoint=keypair_generator(LnKeyFamily.REVOCATION_BASE),
       -            to_self_delay=9,
       -            dust_limit_sat=546,
       -            max_htlc_value_in_flight_msat=funding_sat * 1000,
       -            max_accepted_htlcs=5,
       -            initial_msat=initial_msat,
       -            ctn=-1,
       -            next_htlc_id=0,
       -            reserve_sat=546,
       -            per_commitment_secret_seed=keypair_generator(LnKeyFamily.REVOCATION_ROOT).privkey,
       -            funding_locked_received=False,
       -            was_announced=False,
       -            current_commitment_signature=None,
       -            current_htlc_signatures=[],
       -            got_sig_for_next=False,
       -        )
       -        return local_config
       -
       -    @log_exceptions
       -    async def channel_establishment_flow(self, password: Optional[str], funding_sat: int,
       -                                         push_msat: int, temp_channel_id: bytes) -> Channel:
       -        wallet = self.lnworker.wallet
       -        # dry run creating funding tx to see if we even have enough funds
       -        funding_tx_test = wallet.mktx([TxOutput(bitcoin.TYPE_ADDRESS, wallet.dummy_address(), funding_sat)],
       -                                      password, self.lnworker.config, nonlocal_only=True)
       -        await asyncio.wait_for(self.initialized.wait(), 1)
       -        feerate = self.lnworker.current_feerate_per_kw()
       -        local_config = self.make_local_config(funding_sat, push_msat, LOCAL)
       -        # for the first commitment transaction
       -        per_commitment_secret_first = get_per_commitment_secret_from_seed(local_config.per_commitment_secret_seed,
       -                                                                          RevocationStore.START_INDEX)
       -        per_commitment_point_first = secret_to_pubkey(int.from_bytes(per_commitment_secret_first, 'big'))
       -        self.send_message(
       -            "open_channel",
       -            temporary_channel_id=temp_channel_id,
       -            chain_hash=constants.net.rev_genesis_bytes(),
       -            funding_satoshis=funding_sat,
       -            push_msat=push_msat,
       -            dust_limit_satoshis=local_config.dust_limit_sat,
       -            feerate_per_kw=feerate,
       -            max_accepted_htlcs=local_config.max_accepted_htlcs,
       -            funding_pubkey=local_config.multisig_key.pubkey,
       -            revocation_basepoint=local_config.revocation_basepoint.pubkey,
       -            htlc_basepoint=local_config.htlc_basepoint.pubkey,
       -            payment_basepoint=local_config.payment_basepoint.pubkey,
       -            delayed_payment_basepoint=local_config.delayed_basepoint.pubkey,
       -            first_per_commitment_point=per_commitment_point_first,
       -            to_self_delay=local_config.to_self_delay,
       -            max_htlc_value_in_flight_msat=local_config.max_htlc_value_in_flight_msat,
       -            channel_flags=0x00,  # not willing to announce channel
       -            channel_reserve_satoshis=local_config.reserve_sat,
       -            htlc_minimum_msat=1,
       -        )
       -        payload = await asyncio.wait_for(self.channel_accepted[temp_channel_id].get(), 1)
       -        if payload.get('error'):
       -            raise Exception('Remote Lightning peer reported error: ' + repr(payload.get('error')))
       -        remote_per_commitment_point = payload['first_per_commitment_point']
       -        funding_txn_minimum_depth = int.from_bytes(payload['minimum_depth'], 'big')
       -        assert funding_txn_minimum_depth > 0, funding_txn_minimum_depth
       -        remote_dust_limit_sat = int.from_bytes(payload['dust_limit_satoshis'], byteorder='big')
       -        remote_reserve_sat = self.validate_remote_reserve(payload["channel_reserve_satoshis"], remote_dust_limit_sat, funding_sat)
       -        if remote_dust_limit_sat > remote_reserve_sat:
       -            raise Exception(f"Remote Lightning peer reports dust_limit_sat > reserve_sat which is a BOLT-02 protocol violation.")
       -        htlc_min = int.from_bytes(payload['htlc_minimum_msat'], 'big')
       -        if htlc_min > MAXIMUM_HTLC_MINIMUM_MSAT_ACCEPTED:
       -            raise Exception(f"Remote Lightning peer reports htlc_minimum_msat={htlc_min} mSAT," +
       -                    f" which is above Electrums required maximum limit of that parameter ({MAXIMUM_HTLC_MINIMUM_MSAT_ACCEPTED} mSAT).")
       -        remote_max = int.from_bytes(payload['max_htlc_value_in_flight_msat'], 'big')
       -        if remote_max < MINIMUM_MAX_HTLC_VALUE_IN_FLIGHT_ACCEPTED:
       -            raise Exception(f"Remote Lightning peer reports max_htlc_value_in_flight_msat at only {remote_max} mSAT" +
       -                    f" which is below Electrums required minimum ({MINIMUM_MAX_HTLC_VALUE_IN_FLIGHT_ACCEPTED} mSAT).")
       -        max_accepted_htlcs = int.from_bytes(payload["max_accepted_htlcs"], 'big')
       -        if max_accepted_htlcs > 483:
       -            raise Exception("Remote Lightning peer reports max_accepted_htlcs > 483, which is a BOLT-02 protocol violation.")
       -        remote_to_self_delay = int.from_bytes(payload['to_self_delay'], byteorder='big')
       -        if remote_to_self_delay > MAXIMUM_REMOTE_TO_SELF_DELAY_ACCEPTED:
       -            raise Exception(f"Remote Lightning peer reports to_self_delay={remote_to_self_delay}," +
       -                    f" which is above Electrums required maximum ({MAXIMUM_REMOTE_TO_SELF_DELAY_ACCEPTED})")
       -        their_revocation_store = RevocationStore()
       -        remote_config = RemoteConfig(
       -            payment_basepoint=OnlyPubkeyKeypair(payload['payment_basepoint']),
       -            multisig_key=OnlyPubkeyKeypair(payload["funding_pubkey"]),
       -            htlc_basepoint=OnlyPubkeyKeypair(payload['htlc_basepoint']),
       -            delayed_basepoint=OnlyPubkeyKeypair(payload['delayed_payment_basepoint']),
       -            revocation_basepoint=OnlyPubkeyKeypair(payload['revocation_basepoint']),
       -            to_self_delay=remote_to_self_delay,
       -            dust_limit_sat=remote_dust_limit_sat,
       -            max_htlc_value_in_flight_msat=remote_max,
       -            max_accepted_htlcs=max_accepted_htlcs,
       -            initial_msat=push_msat,
       -            ctn = -1,
       -            next_htlc_id = 0,
       -            reserve_sat = remote_reserve_sat,
       -            htlc_minimum_msat = htlc_min,
       -
       -            next_per_commitment_point=remote_per_commitment_point,
       -            current_per_commitment_point=None,
       -            revocation_store=their_revocation_store,
       -        )
       -        # create funding tx
       -        redeem_script = funding_output_script(local_config, remote_config)
       -        funding_address = bitcoin.redeem_script_to_address('p2wsh', redeem_script)
       -        funding_output = TxOutput(bitcoin.TYPE_ADDRESS, funding_address, funding_sat)
       -        funding_tx = wallet.mktx([funding_output], password, self.lnworker.config, nonlocal_only=True)
       -        funding_txid = funding_tx.txid()
       -        funding_index = funding_tx.outputs().index(funding_output)
       -        # remote commitment transaction
       -        channel_id, funding_txid_bytes = channel_id_from_funding_tx(funding_txid, funding_index)
       -        chan_dict = {
       -                "node_id": self.pubkey,
       -                "channel_id": channel_id,
       -                "short_channel_id": None,
       -                "funding_outpoint": Outpoint(funding_txid, funding_index),
       -                "remote_config": remote_config,
       -                "local_config": local_config,
       -                "constraints": ChannelConstraints(capacity=funding_sat, is_initiator=True, funding_txn_minimum_depth=funding_txn_minimum_depth, feerate=feerate),
       -                "remote_commitment_to_be_revoked": None,
       -        }
       -        chan = Channel(chan_dict,
       -                       sweep_address=self.lnworker.sweep_address,
       -                       payment_completed=self.lnworker.payment_completed)
       -        chan.lnwatcher = self.lnwatcher
       -        chan.get_preimage_and_invoice = self.lnworker.get_invoice  # FIXME hack.
       -        sig_64, _ = chan.sign_next_commitment()
       -        self.send_message("funding_created",
       -            temporary_channel_id=temp_channel_id,
       -            funding_txid=funding_txid_bytes,
       -            funding_output_index=funding_index,
       -            signature=sig_64)
       -        payload = await asyncio.wait_for(self.funding_signed[channel_id].get(), 1)
       -        self.print_error('received funding_signed')
       -        remote_sig = payload['signature']
       -        chan.receive_new_commitment(remote_sig, [])
       -        # broadcast funding tx
       -        await asyncio.wait_for(self.network.broadcast_transaction(funding_tx), 1)
       -        chan.remote_commitment_to_be_revoked = chan.pending_commitment(REMOTE)
       -        chan.config[REMOTE] = chan.config[REMOTE]._replace(ctn=0, current_per_commitment_point=remote_per_commitment_point, next_per_commitment_point=None)
       -        chan.config[LOCAL] = chan.config[LOCAL]._replace(ctn=0, current_commitment_signature=remote_sig, got_sig_for_next=False)
       -        chan.set_state('OPENING')
       -        chan.set_remote_commitment()
       -        chan.set_local_commitment(chan.current_commitment(LOCAL))
       -        return chan
       -
       -    async def on_open_channel(self, payload):
       -        # payload['channel_flags']
       -        if payload['chain_hash'] != constants.net.rev_genesis_bytes():
       -            raise Exception('wrong chain_hash')
       -        funding_sat = int.from_bytes(payload['funding_satoshis'], 'big')
       -        push_msat = int.from_bytes(payload['push_msat'], 'big')
       -        feerate = int.from_bytes(payload['feerate_per_kw'], 'big')
       -
       -        temp_chan_id = payload['temporary_channel_id']
       -        local_config = self.make_local_config(funding_sat, push_msat, REMOTE)
       -        # for the first commitment transaction
       -        per_commitment_secret_first = get_per_commitment_secret_from_seed(local_config.per_commitment_secret_seed,
       -                                                                          RevocationStore.START_INDEX)
       -        per_commitment_point_first = secret_to_pubkey(int.from_bytes(per_commitment_secret_first, 'big'))
       -        min_depth = 3
       -        self.send_message('accept_channel',
       -            temporary_channel_id=temp_chan_id,
       -            dust_limit_satoshis=local_config.dust_limit_sat,
       -            max_htlc_value_in_flight_msat=local_config.max_htlc_value_in_flight_msat,
       -            channel_reserve_satoshis=local_config.reserve_sat,
       -            htlc_minimum_msat=1000,
       -            minimum_depth=min_depth,
       -            to_self_delay=local_config.to_self_delay,
       -            max_accepted_htlcs=local_config.max_accepted_htlcs,
       -            funding_pubkey=local_config.multisig_key.pubkey,
       -            revocation_basepoint=local_config.revocation_basepoint.pubkey,
       -            payment_basepoint=local_config.payment_basepoint.pubkey,
       -            delayed_payment_basepoint=local_config.delayed_basepoint.pubkey,
       -            htlc_basepoint=local_config.htlc_basepoint.pubkey,
       -            first_per_commitment_point=per_commitment_point_first,
       -        )
       -        funding_created = await self.funding_created[temp_chan_id].get()
       -        funding_idx = int.from_bytes(funding_created['funding_output_index'], 'big')
       -        funding_txid = bh2u(funding_created['funding_txid'][::-1])
       -        channel_id, funding_txid_bytes = channel_id_from_funding_tx(funding_txid, funding_idx)
       -        their_revocation_store = RevocationStore()
       -        remote_balance_sat = funding_sat * 1000 - push_msat
       -        remote_dust_limit_sat = int.from_bytes(payload['dust_limit_satoshis'], byteorder='big') # TODO validate
       -        remote_reserve_sat = self.validate_remote_reserve(payload['channel_reserve_satoshis'], remote_dust_limit_sat, funding_sat)
       -        chan_dict = {
       -                "node_id": self.pubkey,
       -                "channel_id": channel_id,
       -                "short_channel_id": None,
       -                "funding_outpoint": Outpoint(funding_txid, funding_idx),
       -                "remote_config": RemoteConfig(
       -                    payment_basepoint=OnlyPubkeyKeypair(payload['payment_basepoint']),
       -                    multisig_key=OnlyPubkeyKeypair(payload['funding_pubkey']),
       -                    htlc_basepoint=OnlyPubkeyKeypair(payload['htlc_basepoint']),
       -                    delayed_basepoint=OnlyPubkeyKeypair(payload['delayed_payment_basepoint']),
       -                    revocation_basepoint=OnlyPubkeyKeypair(payload['revocation_basepoint']),
       -                    to_self_delay=int.from_bytes(payload['to_self_delay'], 'big'),
       -                    dust_limit_sat=remote_dust_limit_sat,
       -                    max_htlc_value_in_flight_msat=int.from_bytes(payload['max_htlc_value_in_flight_msat'], 'big'), # TODO validate
       -                    max_accepted_htlcs=int.from_bytes(payload['max_accepted_htlcs'], 'big'), # TODO validate
       -                    initial_msat=remote_balance_sat,
       -                    ctn = -1,
       -                    next_htlc_id = 0,
       -                    reserve_sat = remote_reserve_sat,
       -                    htlc_minimum_msat=int.from_bytes(payload['htlc_minimum_msat'], 'big'), # TODO validate
       -
       -                    next_per_commitment_point=payload['first_per_commitment_point'],
       -                    current_per_commitment_point=None,
       -                    revocation_store=their_revocation_store,
       -                ),
       -                "local_config": local_config,
       -                "constraints": ChannelConstraints(capacity=funding_sat, is_initiator=False, funding_txn_minimum_depth=min_depth, feerate=feerate),
       -                "remote_commitment_to_be_revoked": None,
       -        }
       -        chan = Channel(chan_dict,
       -                       sweep_address=self.lnworker.sweep_address,
       -                       payment_completed=self.lnworker.payment_completed)
       -        chan.lnwatcher = self.lnwatcher
       -        chan.get_preimage_and_invoice = self.lnworker.get_invoice  # FIXME hack.
       -        remote_sig = funding_created['signature']
       -        chan.receive_new_commitment(remote_sig, [])
       -        sig_64, _ = chan.sign_next_commitment()
       -        self.send_message('funding_signed',
       -            channel_id=channel_id,
       -            signature=sig_64,
       -        )
       -        chan.set_state('OPENING')
       -        chan.remote_commitment_to_be_revoked = chan.pending_commitment(REMOTE)
       -        chan.config[REMOTE] = chan.config[REMOTE]._replace(ctn=0, current_per_commitment_point=payload['first_per_commitment_point'], next_per_commitment_point=None)
       -        chan.config[LOCAL] = chan.config[LOCAL]._replace(ctn=0, current_commitment_signature=remote_sig)
       -        self.lnworker.save_channel(chan)
       -        self.lnwatcher.watch_channel(chan.get_funding_address(), chan.funding_outpoint.to_str())
       -        self.lnworker.on_channels_updated()
       -        while True:
       -            try:
       -                funding_tx = Transaction(await self.network.get_transaction(funding_txid))
       -            except aiorpcx.jsonrpc.RPCError as e:
       -                print("sleeping", str(e))
       -                await asyncio.sleep(1)
       -            else:
       -                break
       -        outp = funding_tx.outputs()[funding_idx]
       -        redeem_script = funding_output_script(chan.config[REMOTE], chan.config[LOCAL])
       -        funding_address = bitcoin.redeem_script_to_address('p2wsh', redeem_script)
       -        if outp != TxOutput(bitcoin.TYPE_ADDRESS, funding_address, funding_sat):
       -            chan.set_state('DISCONNECTED')
       -            raise Exception('funding outpoint mismatch')
       -
       -    def validate_remote_reserve(self, payload_field: bytes, dust_limit: int, funding_sat: int) -> int:
       -        remote_reserve_sat = int.from_bytes(payload_field, 'big')
       -        if remote_reserve_sat < dust_limit:
       -            raise Exception('protocol violation: reserve < dust_limit')
       -        if remote_reserve_sat > funding_sat/100:
       -            raise Exception(f'reserve too high: {remote_reserve_sat}, funding_sat: {funding_sat}')
       -        return remote_reserve_sat
       -
       -    def on_channel_reestablish(self, payload):
       -        chan_id = payload["channel_id"]
       -        self.print_error("Received channel_reestablish", bh2u(chan_id))
       -        chan = self.channels.get(chan_id)
       -        if not chan:
       -            self.print_error("Warning: received unknown channel_reestablish", bh2u(chan_id))
       -            return
       -        self.channel_reestablished[chan_id].set_result(payload)
       -
       -    @log_exceptions
       -    async def reestablish_channel(self, chan: Channel):
       -        await self.initialized.wait()
       -        chan_id = chan.channel_id
       -        if chan.get_state() != 'DISCONNECTED':
       -            self.print_error('reestablish_channel was called but channel {} already in state {}'
       -                             .format(chan_id, chan.get_state()))
       -            return
       -        chan.set_state('REESTABLISHING')
       -        self.network.trigger_callback('channel', chan)
       -        self.send_message("channel_reestablish",
       -            channel_id=chan_id,
       -            next_local_commitment_number=chan.config[LOCAL].ctn+1,
       -            next_remote_revocation_number=chan.config[REMOTE].ctn
       -        )
       -        channel_reestablish_msg = await self.channel_reestablished[chan_id]
       -        chan.set_state('OPENING')
       -
       -        def try_to_get_remote_to_force_close_with_their_latest():
       -            self.print_error("trying to get remote to force close", bh2u(chan_id))
       -            self.send_message("channel_reestablish",
       -                                      channel_id=chan_id,
       -                                      next_local_commitment_number=0,
       -                                      next_remote_revocation_number=0
       -                                      )
       -        # compare remote ctns
       -        remote_ctn = int.from_bytes(channel_reestablish_msg["next_local_commitment_number"], 'big')
       -        if remote_ctn != chan.config[REMOTE].ctn + 1:
       -            self.print_error("expected remote ctn {}, got {}".format(chan.config[REMOTE].ctn + 1, remote_ctn))
       -            # TODO iff their ctn is lower than ours, we should force close instead
       -            try_to_get_remote_to_force_close_with_their_latest()
       -            return
       -        # compare local ctns
       -        local_ctn = int.from_bytes(channel_reestablish_msg["next_remote_revocation_number"], 'big')
       -        if local_ctn != chan.config[LOCAL].ctn:
       -            if remote_ctn == chan.config[LOCAL].ctn + 1:
       -                # A node:
       -                #    if next_remote_revocation_number is equal to the
       -                #    commitment number of the last revoke_and_ack
       -                #    the receiving node sent, AND the receiving node
       -                #    hasn't already received a closing_signed:
       -                #        MUST re-send the revoke_and_ack.
       -                chan.config[LOCAL]=chan.config[LOCAL]._replace(
       -                    ctn=remote_ctn,
       -                )
       -                self.send_revoke_and_ack(chan)
       -            else:
       -                self.print_error("expected local ctn {}, got {}".format(chan.config[LOCAL].ctn, local_ctn))
       -                # TODO iff their ctn is lower than ours, we should force close instead
       -                try_to_get_remote_to_force_close_with_their_latest()
       -                return
       -        # compare per commitment points (needs data_protect option)
       -        their_pcp = channel_reestablish_msg.get("my_current_per_commitment_point", None)
       -        if their_pcp is not None:
       -            our_pcp = chan.config[REMOTE].current_per_commitment_point
       -            if our_pcp is None:
       -                our_pcp = chan.config[REMOTE].next_per_commitment_point
       -            if our_pcp != their_pcp:
       -                self.print_error("Remote PCP mismatch: {} {}".format(bh2u(our_pcp), bh2u(their_pcp)))
       -                # FIXME ...what now?
       -                try_to_get_remote_to_force_close_with_their_latest()
       -                return
       -        if remote_ctn == chan.config[LOCAL].ctn+1 == 1 and chan.short_channel_id:
       -            self.send_funding_locked(chan)
       -        # checks done
       -        if chan.config[LOCAL].funding_locked_received and chan.short_channel_id:
       -            self.mark_open(chan)
       -        self.network.trigger_callback('channel', chan)
       -
       -    def send_funding_locked(self, chan: Channel):
       -        channel_id = chan.channel_id
       -        per_commitment_secret_index = RevocationStore.START_INDEX - 1
       -        per_commitment_point_second = secret_to_pubkey(int.from_bytes(
       -            get_per_commitment_secret_from_seed(chan.config[LOCAL].per_commitment_secret_seed, per_commitment_secret_index), 'big'))
       -        # note: if funding_locked was not yet received, we might send it multiple times
       -        self.send_message("funding_locked", channel_id=channel_id, next_per_commitment_point=per_commitment_point_second)
       -        if chan.config[LOCAL].funding_locked_received and chan.short_channel_id:
       -            self.mark_open(chan)
       -
       -    def on_funding_locked(self, payload):
       -        channel_id = payload['channel_id']
       -        chan = self.channels.get(channel_id)
       -        if not chan:
       -            print(self.channels)
       -            raise Exception("Got unknown funding_locked", channel_id)
       -        if not chan.config[LOCAL].funding_locked_received:
       -            our_next_point = chan.config[REMOTE].next_per_commitment_point
       -            their_next_point = payload["next_per_commitment_point"]
       -            new_remote_state = chan.config[REMOTE]._replace(next_per_commitment_point=their_next_point)
       -            new_local_state = chan.config[LOCAL]._replace(funding_locked_received = True)
       -            chan.config[REMOTE]=new_remote_state
       -            chan.config[LOCAL]=new_local_state
       -            self.lnworker.save_channel(chan)
       -        if chan.short_channel_id:
       -            self.mark_open(chan)
       -
       -    def on_network_update(self, chan: Channel, funding_tx_depth: int):
       -        """
       -        Only called when the channel is OPEN.
       -
       -        Runs on the Network thread.
       -        """
       -        if not chan.config[LOCAL].was_announced and funding_tx_depth >= 6:
       -            # don't announce our channels
       -            # FIXME should this be a field in chan.local_state maybe?
       -            return
       -            chan.config[LOCAL]=chan.config[LOCAL]._replace(was_announced=True)
       -            coro = self.handle_announcements(chan)
       -            self.lnworker.save_channel(chan)
       -            asyncio.run_coroutine_threadsafe(coro, self.network.asyncio_loop)
       -
       -    @log_exceptions
       -    async def handle_announcements(self, chan):
       -        h, local_node_sig, local_bitcoin_sig = self.send_announcement_signatures(chan)
       -        announcement_signatures_msg = await self.announcement_signatures[chan.channel_id].get()
       -        remote_node_sig = announcement_signatures_msg["node_signature"]
       -        remote_bitcoin_sig = announcement_signatures_msg["bitcoin_signature"]
       -        if not ecc.verify_signature(chan.config[REMOTE].multisig_key.pubkey, remote_bitcoin_sig, h):
       -            raise Exception("bitcoin_sig invalid in announcement_signatures")
       -        if not ecc.verify_signature(self.pubkey, remote_node_sig, h):
       -            raise Exception("node_sig invalid in announcement_signatures")
       -
       -        node_sigs = [remote_node_sig, local_node_sig]
       -        bitcoin_sigs = [remote_bitcoin_sig, local_bitcoin_sig]
       -        bitcoin_keys = [chan.config[REMOTE].multisig_key.pubkey, chan.config[LOCAL].multisig_key.pubkey]
       -
       -        if self.node_ids[0] > self.node_ids[1]:
       -            node_sigs.reverse()
       -            bitcoin_sigs.reverse()
       -            node_ids = list(reversed(self.node_ids))
       -            bitcoin_keys.reverse()
       -        else:
       -            node_ids = self.node_ids
       -
       -        self.send_message("channel_announcement",
       -            node_signatures_1=node_sigs[0],
       -            node_signatures_2=node_sigs[1],
       -            bitcoin_signature_1=bitcoin_sigs[0],
       -            bitcoin_signature_2=bitcoin_sigs[1],
       -            len=0,
       -            #features not set (defaults to zeros)
       -            chain_hash=constants.net.rev_genesis_bytes(),
       -            short_channel_id=chan.short_channel_id,
       -            node_id_1=node_ids[0],
       -            node_id_2=node_ids[1],
       -            bitcoin_key_1=bitcoin_keys[0],
       -            bitcoin_key_2=bitcoin_keys[1]
       -        )
       -
       -        print("SENT CHANNEL ANNOUNCEMENT")
       -
       -    def mark_open(self, chan: Channel):
       -        assert chan.short_channel_id is not None
       -        if chan.get_state() == "OPEN":
       -            return
       -        # NOTE: even closed channels will be temporarily marked "OPEN"
       -        assert chan.config[LOCAL].funding_locked_received
       -        chan.set_state("OPEN")
       -        self.network.trigger_callback('channel', chan)
       -        # add channel to database
       -        bitcoin_keys = [chan.config[LOCAL].multisig_key.pubkey, chan.config[REMOTE].multisig_key.pubkey]
       -        sorted_node_ids = list(sorted(self.node_ids))
       -        if sorted_node_ids != self.node_ids:
       -            node_ids = sorted_node_ids
       -            bitcoin_keys.reverse()
       -        else:
       -            node_ids = self.node_ids
       -        # note: we inject a channel announcement, and a channel update (for outgoing direction)
       -        # This is atm needed for
       -        # - finding routes
       -        # - the ChanAnn is needed so that we can anchor to it a future ChanUpd
       -        #   that the remote sends, even if the channel was not announced
       -        #   (from BOLT-07: "MAY create a channel_update to communicate the channel
       -        #    parameters to the final node, even though the channel has not yet been announced")
       -        self.channel_db.on_channel_announcement({"short_channel_id": chan.short_channel_id, "node_id_1": node_ids[0], "node_id_2": node_ids[1],
       -                                                 'chain_hash': constants.net.rev_genesis_bytes(), 'len': b'\x00\x00', 'features': b'',
       -                                                 'bitcoin_key_1': bitcoin_keys[0], 'bitcoin_key_2': bitcoin_keys[1]},
       -                                                trusted=True)
       -        # only inject outgoing direction:
       -        if node_ids[0] == privkey_to_pubkey(self.privkey):
       -            channel_flags = b'\x00'
       -        else:
       -            channel_flags = b'\x01'
       -        now = int(time.time()).to_bytes(4, byteorder="big")
       -        self.channel_db.on_channel_update({"short_channel_id": chan.short_channel_id, 'channel_flags': channel_flags, 'cltv_expiry_delta': b'\x90',
       -                                           'htlc_minimum_msat': b'\x03\xe8', 'fee_base_msat': b'\x03\xe8', 'fee_proportional_millionths': b'\x01',
       -                                           'chain_hash': constants.net.rev_genesis_bytes(), 'timestamp': now},
       -                                          trusted=True)
       -        # peer may have sent us a channel update for the incoming direction previously
       -        # note: if we were offline when the 3rd conf happened, lnd will never send us this channel_update
       -        # see https://github.com/lightningnetwork/lnd/issues/1347
       -        #self.send_message("query_short_channel_ids", chain_hash=constants.net.rev_genesis_bytes(),
       -        #                          len=9, encoded_short_ids=b'\x00'+chan.short_channel_id)
       -        pending_channel_update = self.orphan_channel_updates.get(chan.short_channel_id)
       -        if pending_channel_update:
       -            self.channel_db.on_channel_update(pending_channel_update)
       -
       -        self.print_error("CHANNEL OPENING COMPLETED")
       -
       -    def send_announcement_signatures(self, chan: Channel):
       -
       -        bitcoin_keys = [chan.config[REMOTE].multisig_key.pubkey,
       -                        chan.config[LOCAL].multisig_key.pubkey]
       -
       -        sorted_node_ids = list(sorted(self.node_ids))
       -        if sorted_node_ids != self.node_ids:
       -            node_ids = sorted_node_ids
       -            bitcoin_keys.reverse()
       -        else:
       -            node_ids = self.node_ids
       -
       -        chan_ann = encode_msg("channel_announcement",
       -            len=0,
       -            #features not set (defaults to zeros)
       -            chain_hash=constants.net.rev_genesis_bytes(),
       -            short_channel_id=chan.short_channel_id,
       -            node_id_1=node_ids[0],
       -            node_id_2=node_ids[1],
       -            bitcoin_key_1=bitcoin_keys[0],
       -            bitcoin_key_2=bitcoin_keys[1]
       -        )
       -        to_hash = chan_ann[256+2:]
       -        h = sha256d(to_hash)
       -        bitcoin_signature = ecc.ECPrivkey(chan.config[LOCAL].multisig_key.privkey).sign(h, sig_string_from_r_and_s, get_r_and_s_from_sig_string)
       -        node_signature = ecc.ECPrivkey(self.privkey).sign(h, sig_string_from_r_and_s, get_r_and_s_from_sig_string)
       -        self.send_message("announcement_signatures",
       -            channel_id=chan.channel_id,
       -            short_channel_id=chan.short_channel_id,
       -            node_signature=node_signature,
       -            bitcoin_signature=bitcoin_signature
       -        )
       -
       -        return h, node_signature, bitcoin_signature
       -
       -    @log_exceptions
       -    async def on_update_fail_htlc(self, payload):
       -        channel_id = payload["channel_id"]
       -        htlc_id = int.from_bytes(payload["id"], "big")
       -        key = (channel_id, htlc_id)
       -        try:
       -            route = self.attempted_route[key]
       -        except KeyError:
       -            # the remote might try to fail an htlc after we restarted...
       -            # attempted_route is not persisted, so we will get here then
       -            self.print_error("UPDATE_FAIL_HTLC. cannot decode! attempted route is MISSING. {}".format(key))
       -        else:
       -            try:
       -                await self._handle_error_code_from_failed_htlc(payload["reason"], route, channel_id, htlc_id)
       -            except Exception:
       -                # exceptions are suppressed as failing to handle an error code
       -                # should not block us from removing the htlc
       -                traceback.print_exc(file=sys.stderr)
       -        # process update_fail_htlc on channel
       -        chan = self.channels[channel_id]
       -        chan.receive_fail_htlc(htlc_id)
       -        await self.receive_and_revoke(chan)
       -        self.network.trigger_callback('ln_message', self.lnworker, 'Payment failed', htlc_id)
       -
       -    async def _handle_error_code_from_failed_htlc(self, error_reason, route: List['RouteEdge'], channel_id, htlc_id):
       -        chan = self.channels[channel_id]
       -        failure_msg, sender_idx = decode_onion_error(error_reason,
       -                                                     [x.node_id for x in route],
       -                                                     chan.onion_keys[htlc_id])
       -        code, data = failure_msg.code, failure_msg.data
       -        self.print_error("UPDATE_FAIL_HTLC", repr(code), data)
       -        self.print_error(f"error reported by {bh2u(route[sender_idx].node_id)}")
       -        # handle some specific error codes
       -        failure_codes = {
       -            OnionFailureCode.TEMPORARY_CHANNEL_FAILURE: 2,
       -            OnionFailureCode.AMOUNT_BELOW_MINIMUM: 10,
       -            OnionFailureCode.FEE_INSUFFICIENT: 10,
       -            OnionFailureCode.INCORRECT_CLTV_EXPIRY: 6,
       -            OnionFailureCode.EXPIRY_TOO_SOON: 2,
       -            OnionFailureCode.CHANNEL_DISABLED: 4,
       -        }
       -        offset = failure_codes.get(code)
       -        if offset:
       -            channel_update = (258).to_bytes(length=2, byteorder="big") + data[offset:]
       -            message_type, payload = decode_msg(channel_update)
       -            try:
       -                self.print_error("trying to apply channel update on our db", payload)
       -                self.channel_db.on_channel_update(payload)
       -                self.print_error("successfully applied channel update on our db")
       -            except NotFoundChanAnnouncementForUpdate:
       -                # maybe it is a private channel (and data in invoice was outdated)
       -                self.print_error("maybe channel update is for private channel?")
       -                start_node_id = route[sender_idx].node_id
       -                self.channel_db.add_channel_update_for_private_channel(payload, start_node_id)
       -        else:
       -            # blacklist channel after reporter node
       -            # TODO this should depend on the error (even more granularity)
       -            # also, we need finer blacklisting (directed edges; nodes)
       -            try:
       -                short_chan_id = route[sender_idx + 1].short_channel_id
       -            except IndexError:
       -                self.print_error("payment destination reported error")
       -            else:
       -                self.network.path_finder.blacklist.add(short_chan_id)
       -
       -    def send_commitment(self, chan: Channel):
       -        sig_64, htlc_sigs = chan.sign_next_commitment()
       -        self.send_message("commitment_signed", channel_id=chan.channel_id, signature=sig_64, num_htlcs=len(htlc_sigs), htlc_signature=b"".join(htlc_sigs))
       -        return len(htlc_sigs)
       -
       -    async def send_and_revoke(self, chan: Channel):
       -        """ generic channel update flow """
       -        self.send_commitment(chan)
       -        await self.receive_revoke_and_ack(chan)
       -        await self.receive_commitment(chan)
       -        self.send_revoke_and_ack(chan)
       -
       -    async def receive_and_revoke(self, chan: Channel):
       -        await self.receive_commitment(chan)
       -        self.send_revoke_and_ack(chan)
       -        self.send_commitment(chan)
       -        await self.receive_revoke_and_ack(chan)
       -
       -    async def pay(self, route: List['RouteEdge'], chan: Channel, amount_msat: int,
       -                  payment_hash: bytes, min_final_cltv_expiry: int):
       -        assert chan.get_state() == "OPEN", chan.get_state()
       -        assert amount_msat > 0, "amount_msat is not greater zero"
       -        # create onion packet
       -        final_cltv = self.network.get_local_height() + min_final_cltv_expiry
       -        hops_data, amount_msat, cltv = calc_hops_data_for_payment(route, amount_msat, final_cltv)
       -        assert final_cltv <= cltv, (final_cltv, cltv)
       -        secret_key = os.urandom(32)
       -        onion = new_onion_packet([x.node_id for x in route], secret_key, hops_data, associated_data=payment_hash)
       -        # create htlc
       -        htlc = {'amount_msat':amount_msat, 'payment_hash':payment_hash, 'cltv_expiry':cltv}
       -        htlc_id = chan.add_htlc(htlc)
       -        chan.onion_keys[htlc_id] = secret_key
       -        self.attempted_route[(chan.channel_id, htlc_id)] = route
       -        self.print_error(f"starting payment. route: {route}")
       -        self.send_message("update_add_htlc",
       -                          channel_id=chan.channel_id,
       -                          id=htlc_id,
       -                          cltv_expiry=cltv,
       -                          amount_msat=amount_msat,
       -                          payment_hash=payment_hash,
       -                          onion_routing_packet=onion.to_bytes())
       -        await self.send_and_revoke(chan)
       -        return UpdateAddHtlc(**htlc, htlc_id=htlc_id)
       -
       -    async def receive_revoke_and_ack(self, chan: Channel):
       -        revoke_and_ack_msg = await self.revoke_and_ack[chan.channel_id].get()
       -        chan.receive_revocation(RevokeAndAck(revoke_and_ack_msg["per_commitment_secret"], revoke_and_ack_msg["next_per_commitment_point"]))
       -        self.lnworker.save_channel(chan)
       -
       -    def send_revoke_and_ack(self, chan: Channel):
       -        rev, _ = chan.revoke_current_commitment()
       -        self.lnworker.save_channel(chan)
       -        self.send_message("revoke_and_ack",
       -            channel_id=chan.channel_id,
       -            per_commitment_secret=rev.per_commitment_secret,
       -            next_per_commitment_point=rev.next_per_commitment_point)
       -
       -    async def receive_commitment(self, chan: Channel, commitment_signed_msg=None):
       -        if commitment_signed_msg is None:
       -            commitment_signed_msg = await self.commitment_signed[chan.channel_id].get()
       -        data = commitment_signed_msg["htlc_signature"]
       -        htlc_sigs = [data[i:i+64] for i in range(0, len(data), 64)]
       -        chan.receive_new_commitment(commitment_signed_msg["signature"], htlc_sigs)
       -        return len(htlc_sigs)
       -
       -    def on_commitment_signed(self, payload):
       -        self.print_error("commitment_signed", payload)
       -        channel_id = payload['channel_id']
       -        self.commitment_signed[channel_id].put_nowait(payload)
       -
       -    @log_exceptions
       -    async def on_update_fulfill_htlc(self, update_fulfill_htlc_msg):
       -        self.print_error("update_fulfill")
       -        chan = self.channels[update_fulfill_htlc_msg["channel_id"]]
       -        preimage = update_fulfill_htlc_msg["payment_preimage"]
       -        htlc_id = int.from_bytes(update_fulfill_htlc_msg["id"], "big")
       -        chan.receive_htlc_settle(preimage, htlc_id)
       -        await self.receive_and_revoke(chan)
       -        self.network.trigger_callback('ln_message', self.lnworker, 'Payment sent', htlc_id)
       -        # used in lightning-integration
       -        self.payment_preimages[sha256(preimage)].put_nowait(preimage)
       -
       -    def on_update_fail_malformed_htlc(self, payload):
       -        self.print_error("error", payload["data"].decode("ascii"))
       -
       -    @log_exceptions
       -    async def on_update_add_htlc(self, payload):
       -        # no onion routing for the moment: we assume we are the end node
       -        self.print_error('on_update_add_htlc')
       -        # check if this in our list of requests
       -        payment_hash = payload["payment_hash"]
       -        channel_id = payload['channel_id']
       -        htlc_id = int.from_bytes(payload["id"], 'big')
       -        cltv_expiry = int.from_bytes(payload["cltv_expiry"], 'big')
       -        amount_msat_htlc = int.from_bytes(payload["amount_msat"], 'big')
       -        onion_packet = OnionPacket.from_bytes(payload["onion_routing_packet"])
       -        processed_onion = process_onion_packet(onion_packet, associated_data=payment_hash, our_onion_private_key=self.privkey)
       -        chan = self.channels[channel_id]
       -        assert chan.get_state() == "OPEN"
       -        assert htlc_id == chan.config[REMOTE].next_htlc_id, (htlc_id, chan.config[REMOTE].next_htlc_id)  # TODO fail channel instead
       -        if cltv_expiry >= 500_000_000:
       -            pass  # TODO fail the channel
       -        # add htlc
       -        htlc = {'amount_msat': amount_msat_htlc, 'payment_hash':payment_hash, 'cltv_expiry':cltv_expiry}
       -        htlc_id = chan.receive_htlc(htlc)
       -        await self.receive_and_revoke(chan)
       -        # Forward HTLC
       -        # FIXME: this is not robust to us going offline before payment is fulfilled
       -        if not processed_onion.are_we_final:
       -            dph = processed_onion.hop_data.per_hop
       -            next_chan = self.lnworker.get_channel_by_short_id(dph.short_channel_id)
       -            next_peer = self.lnworker.peers[next_chan.node_id]
       -            if next_chan is None or next_chan.get_state() != 'OPEN':
       -                self.print_error("cannot forward htlc", next_chan.get_state() if next_chan else None)
       -                reason = OnionRoutingFailureMessage(code=OnionFailureCode.PERMANENT_CHANNEL_FAILURE, data=b'')
       -                await self.fail_htlc(chan, htlc_id, onion_packet, reason)
       -                return
       -            self.print_error('forwarding htlc to', next_chan.node_id)
       -            next_cltv_expiry = int.from_bytes(dph.outgoing_cltv_value, 'big')
       -            next_amount_msat_htlc = int.from_bytes(dph.amt_to_forward, 'big')
       -            next_htlc = {'amount_msat':next_amount_msat_htlc, 'payment_hash':payment_hash, 'cltv_expiry':next_cltv_expiry}
       -            next_htlc_id = next_chan.add_htlc(next_htlc)
       -            next_peer.send_message(
       -                "update_add_htlc",
       -                channel_id=next_chan.channel_id,
       -                id=next_htlc_id,
       -                cltv_expiry=dph.outgoing_cltv_value,
       -                amount_msat=dph.amt_to_forward,
       -                payment_hash=payment_hash,
       -                onion_routing_packet=processed_onion.next_packet.to_bytes()
       -            )
       -            await next_peer.send_and_revoke(next_chan)
       -            # wait until we get paid
       -            preimage = await next_peer.payment_preimages[payment_hash].get()
       -            # fulfill the original htlc
       -            await self.fulfill_htlc(chan, htlc_id, preimage)
       -            self.print_error("htlc forwarded successfully")
       -            return
       -        try:
       -            preimage, invoice = self.lnworker.get_invoice(payment_hash)
       -        except UnknownPaymentHash:
       -            reason = OnionRoutingFailureMessage(code=OnionFailureCode.UNKNOWN_PAYMENT_HASH, data=b'')
       -            await self.fail_htlc(chan, htlc_id, onion_packet, reason)
       -            return
       -        expected_received_msat = int(invoice.amount * bitcoin.COIN * 1000) if invoice.amount is not None else None
       -        if expected_received_msat is not None and \
       -                (amount_msat_htlc < expected_received_msat or amount_msat_htlc > 2 * expected_received_msat):
       -            reason = OnionRoutingFailureMessage(code=OnionFailureCode.INCORRECT_PAYMENT_AMOUNT, data=b'')
       -            await self.fail_htlc(chan, htlc_id, onion_packet, reason)
       -            return
       -        local_height = self.network.get_local_height()
       -        if local_height + MIN_FINAL_CLTV_EXPIRY_ACCEPTED > cltv_expiry:
       -            reason = OnionRoutingFailureMessage(code=OnionFailureCode.FINAL_EXPIRY_TOO_SOON, data=b'')
       -            await self.fail_htlc(chan, htlc_id, onion_packet, reason)
       -            return
       -        cltv_from_onion = int.from_bytes(processed_onion.hop_data.per_hop.outgoing_cltv_value, byteorder="big")
       -        if cltv_from_onion != cltv_expiry:
       -            reason = OnionRoutingFailureMessage(code=OnionFailureCode.FINAL_INCORRECT_CLTV_EXPIRY,
       -                                                data=cltv_expiry.to_bytes(4, byteorder="big"))
       -            await self.fail_htlc(chan, htlc_id, onion_packet, reason)
       -            return
       -        amount_from_onion = int.from_bytes(processed_onion.hop_data.per_hop.amt_to_forward, byteorder="big")
       -        if amount_from_onion > amount_msat_htlc:
       -            reason = OnionRoutingFailureMessage(code=OnionFailureCode.FINAL_INCORRECT_HTLC_AMOUNT,
       -                                                data=amount_msat_htlc.to_bytes(8, byteorder="big"))
       -            await self.fail_htlc(chan, htlc_id, onion_packet, reason)
       -            return
       -        self.network.trigger_callback('htlc_added', UpdateAddHtlc(**htlc, htlc_id=htlc_id), invoice, RECEIVED)
       -        # settle htlc
       -        if not self.network.config.debug_lightning_do_not_settle:
       -            # settle htlc
       -            await self.fulfill_htlc(chan, htlc_id, preimage)
       -
       -    async def fulfill_htlc(self, chan: Channel, htlc_id: int, preimage: bytes):
       -        chan.settle_htlc(preimage, htlc_id)
       -        self.send_message("update_fulfill_htlc",
       -                          channel_id=chan.channel_id,
       -                          id=htlc_id,
       -                          payment_preimage=preimage)
       -        await self.send_and_revoke(chan)
       -        self.network.trigger_callback('ln_message', self.lnworker, 'Payment received', htlc_id)
       -
       -    async def fail_htlc(self, chan: Channel, htlc_id: int, onion_packet: OnionPacket,
       -                        reason: OnionRoutingFailureMessage):
       -        self.print_error(f"failing received htlc {(bh2u(chan.channel_id), htlc_id)}. reason: {reason}")
       -        chan.fail_htlc(htlc_id)
       -        error_packet = construct_onion_error(reason, onion_packet, our_onion_private_key=self.privkey)
       -        self.send_message("update_fail_htlc",
       -                          channel_id=chan.channel_id,
       -                          id=htlc_id,
       -                          len=len(error_packet),
       -                          reason=error_packet)
       -        await self.send_and_revoke(chan)
       -
       -    def on_revoke_and_ack(self, payload):
       -        self.print_error("got revoke_and_ack")
       -        channel_id = payload["channel_id"]
       -        self.revoke_and_ack[channel_id].put_nowait(payload)
       -
       -    def on_update_fee(self, payload):
       -        channel_id = payload["channel_id"]
       -        feerate =int.from_bytes(payload["feerate_per_kw"], "big")
       -        self.channels[channel_id].update_fee(feerate, False)
       -
       -    async def bitcoin_fee_update(self, chan: Channel):
       -        """
       -        called when our fee estimates change
       -        """
       -        if not chan.constraints.is_initiator:
       -            # TODO force close if initiator does not update_fee enough
       -            return
       -        feerate_per_kw = self.lnworker.current_feerate_per_kw()
       -        chan_fee = chan.pending_feerate(REMOTE)
       -        self.print_error("current pending feerate", chan_fee)
       -        self.print_error("new feerate", feerate_per_kw)
       -        if feerate_per_kw < chan_fee / 2:
       -            self.print_error("FEES HAVE FALLEN")
       -        elif feerate_per_kw > chan_fee * 2:
       -            self.print_error("FEES HAVE RISEN")
       -        else:
       -            return
       -        chan.update_fee(feerate_per_kw, True)
       -        self.send_message("update_fee",
       -                          channel_id=chan.channel_id,
       -                          feerate_per_kw=feerate_per_kw)
       -        await self.send_and_revoke(chan)
       -
       -    def on_closing_signed(self, payload):
       -        chan_id = payload["channel_id"]
       -        if chan_id not in self.closing_signed: raise Exception("Got unknown closing_signed")
       -        self.closing_signed[chan_id].put_nowait(payload)
       -
       -    @log_exceptions
       -    async def close_channel(self, chan_id: bytes):
       -        chan = self.channels[chan_id]
       -        self.shutdown_received[chan_id] = asyncio.Future()
       -        self.send_shutdown(chan)
       -        payload = await self.shutdown_received[chan_id]
       -        txid = await self._shutdown(chan, payload, True)
       -        self.print_error('Channel closed', txid)
       -        return txid
       -
       -    @log_exceptions
       -    async def on_shutdown(self, payload):
       -        # length of scripts allowed in BOLT-02
       -        if int.from_bytes(payload['len'], 'big') not in (3+20+2, 2+20+1, 2+20, 2+32):
       -            raise Exception('scriptpubkey length in received shutdown message invalid: ' + str(payload['len']))
       -        chan_id = payload['channel_id']
       -        if chan_id in self.shutdown_received:
       -            self.shutdown_received[chan_id].set_result(payload)
       -        else:
       -            chan = self.channels[chan_id]
       -            self.send_shutdown(chan)
       -            txid = await self._shutdown(chan, payload, False)
       -            self.print_error('Channel closed by remote peer', txid)
       -
       -    def send_shutdown(self, chan: Channel):
       -        scriptpubkey = bfh(bitcoin.address_to_script(chan.sweep_address))
       -        self.send_message('shutdown', channel_id=chan.channel_id, len=len(scriptpubkey), scriptpubkey=scriptpubkey)
       -
       -    @log_exceptions
       -    async def _shutdown(self, chan: Channel, payload, is_local):
       -        # set state so that we stop accepting HTLCs
       -        chan.set_state('CLOSING')
       -        while len(chan.hm.htlcs_by_direction(LOCAL, RECEIVED)) > 0:
       -            self.print_error('waiting for htlcs to settle...')
       -            await asyncio.sleep(1)
       -        our_fee = chan.pending_local_fee()
       -        scriptpubkey = bfh(bitcoin.address_to_script(chan.sweep_address))
       -        # negociate fee
       -        while True:
       -            our_sig, closing_tx = chan.make_closing_tx(scriptpubkey, payload['scriptpubkey'], fee_sat=our_fee)
       -            self.send_message('closing_signed', channel_id=chan.channel_id, fee_satoshis=our_fee, signature=our_sig)
       -            cs_payload = await asyncio.wait_for(self.closing_signed[chan.channel_id].get(), 1)
       -            their_fee = int.from_bytes(cs_payload['fee_satoshis'], 'big')
       -            their_sig = cs_payload['signature']
       -            if our_fee == their_fee:
       -                break
       -            # TODO: negociate better
       -            our_fee = their_fee
       -        # index of our_sig
       -        i = bool(chan.get_local_index())
       -        if not is_local: i = not i
       -        # add signatures
       -        closing_tx.add_signature_to_txin(0, int(i), bh2u(der_sig_from_sig_string(our_sig) + b'\x01'))
       -        closing_tx.add_signature_to_txin(0, int(not i), bh2u(der_sig_from_sig_string(their_sig) + b'\x01'))
       -        # broadcast
       -        await self.network.broadcast_transaction(closing_tx)
       -        return closing_tx.txid()
   DIR diff --git a/electrum/lnchan.py b/electrum/lnchan.py
       t@@ -1,810 +0,0 @@
       -# Copyright (C) 2018 The Electrum developers
       -# Copyright (C) 2015-2018 The Lightning Network Developers
       -#
       -# Permission is hereby granted, free of charge, to any person obtaining a copy
       -# of this software and associated documentation files (the "Software"), to deal
       -# in the Software without restriction, including without limitation the rights
       -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
       -# copies of the Software, and to permit persons to whom the Software is
       -# furnished to do so, subject to the following conditions:
       -#
       -# The above copyright notice and this permission notice shall be included in
       -# all copies or substantial portions of the Software.
       -#
       -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
       -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
       -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
       -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
       -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
       -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
       -# THE SOFTWARE.
       -
       -# API (method signatures and docstrings) partially copied from lnd
       -# 42de4400bff5105352d0552155f73589166d162b
       -
       -import os
       -from collections import namedtuple, defaultdict
       -import binascii
       -import json
       -from enum import Enum, auto
       -from typing import Optional, Dict, List, Tuple, NamedTuple, Set, Callable, Iterable, Sequence
       -
       -from . import ecc
       -from .util import bfh, PrintError, bh2u
       -from .bitcoin import TYPE_SCRIPT, TYPE_ADDRESS
       -from .bitcoin import redeem_script_to_address
       -from .crypto import sha256, sha256d
       -from .simple_config import get_config
       -from .transaction import Transaction
       -
       -from .lnutil import (Outpoint, LocalConfig, RemoteConfig, Keypair, OnlyPubkeyKeypair, ChannelConstraints,
       -                    get_per_commitment_secret_from_seed, secret_to_pubkey, derive_privkey, make_closing_tx,
       -                    sign_and_get_sig_string, RevocationStore, derive_blinded_pubkey, Direction, derive_pubkey,
       -                    make_htlc_tx_with_open_channel, make_commitment, make_received_htlc, make_offered_htlc,
       -                    HTLC_TIMEOUT_WEIGHT, HTLC_SUCCESS_WEIGHT, extract_ctn_from_tx_and_chan, UpdateAddHtlc,
       -                    funding_output_script, SENT, RECEIVED, LOCAL, REMOTE, HTLCOwner, make_commitment_outputs,
       -                    ScriptHtlc, PaymentFailure, calc_onchain_fees, RemoteMisbehaving, make_htlc_output_witness_script)
       -from .lnsweep import create_sweeptxs_for_their_just_revoked_ctx
       -from .lnsweep import create_sweeptxs_for_our_latest_ctx, create_sweeptxs_for_their_latest_ctx
       -from .lnhtlc import HTLCManager
       -
       -
       -class ChannelJsonEncoder(json.JSONEncoder):
       -    def default(self, o):
       -        if isinstance(o, bytes):
       -            return binascii.hexlify(o).decode("ascii")
       -        if isinstance(o, RevocationStore):
       -            return o.serialize()
       -        if isinstance(o, set):
       -            return list(o)
       -        return super().default(o)
       -
       -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
       -
       -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])
       -
       -# following two functions are used because json
       -# doesn't store int keys and byte string values
       -def str_bytes_dict_from_save(x):
       -    return {int(k): bfh(v) for k,v in x.items()}
       -
       -def str_bytes_dict_to_save(x):
       -    return {str(k): bh2u(v) for k, v in x.items()}
       -
       -class Channel(PrintError):
       -    def diagnostic_name(self):
       -        if self.name:
       -            return str(self.name)
       -        try:
       -            return f"lnchan_{bh2u(self.channel_id[-4:])}"
       -        except:
       -            return super().diagnostic_name()
       -
       -    def __init__(self, state, sweep_address = None, name = None, payment_completed : Optional[Callable[[Direction, UpdateAddHtlc, bytes], None]] = None):
       -        self.preimages = {}
       -        if not payment_completed:
       -            payment_completed = lambda this, x, y, z: None
       -        self.sweep_address = sweep_address
       -        self.payment_completed = payment_completed
       -        assert 'local_state' not in state
       -        self.config = {}
       -        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 = str_bytes_dict_from_save(state.get('onion_keys', {}))
       -
       -        # 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"])
       -        self.remote_commitment_to_be_revoked.deserialize(True)
       -
       -        self.hm = HTLCManager(state.get('log'))
       -
       -        self.name = name
       -
       -        self.pending_fee = None
       -
       -        self._is_funding_txo_spent = None  # "don't know"
       -        self._state = None
       -        if state.get('force_closed', False):
       -            self.set_state('FORCE_CLOSING')
       -        else:
       -            self.set_state('DISCONNECTED')
       -
       -        self.lnwatcher = None
       -
       -        self.local_commitment = None
       -        self.remote_commitment = None
       -
       -    def get_payments(self):
       -        out = {}
       -        for subject in LOCAL, REMOTE:
       -            log = self.hm.log[subject]
       -            for htlc_id, htlc in log.get('adds', {}).items():
       -                rhash = bh2u(htlc.payment_hash)
       -                status = 'settled' if htlc_id in log.get('settles',{}) else 'inflight'
       -                direction = SENT if subject is LOCAL else RECEIVED
       -                out[rhash] = (self.channel_id, htlc, direction, status)
       -        return out
       -
       -    def set_local_commitment(self, ctx):
       -        ctn = extract_ctn_from_tx_and_chan(ctx, self)
       -        assert self.signature_fits(ctx), (self.log[LOCAL])
       -        self.local_commitment = ctx
       -        if self.sweep_address is not None:
       -            self.local_sweeptxs = create_sweeptxs_for_our_latest_ctx(self, self.local_commitment, self.sweep_address)
       -            initial = os.path.join(get_config().electrum_path(), 'initial_commitment_tx')
       -            tx = self.force_close_tx().serialize_to_network()
       -            if not os.path.exists(initial):
       -                with open(initial, 'w') as f:
       -                    f.write(tx)
       -
       -    def set_remote_commitment(self):
       -        self.remote_commitment = self.current_commitment(REMOTE)
       -        if self.sweep_address is not None:
       -            self.remote_sweeptxs = create_sweeptxs_for_their_latest_ctx(self, self.remote_commitment, self.sweep_address)
       -
       -    def set_state(self, state: str):
       -        if self._state == 'FORCE_CLOSING':
       -            assert state == 'FORCE_CLOSING', 'new state was not FORCE_CLOSING: ' + state
       -        self._state = state
       -
       -    def get_state(self):
       -        return self._state
       -
       -    def is_closed(self):
       -        return self.get_state() in ['CLOSED', 'FORCE_CLOSING']
       -
       -    def _check_can_pay(self, amount_msat: int) -> None:
       -        if self.get_state() != 'OPEN':
       -            raise PaymentFailure('Channel not open')
       -        if self.available_to_spend(LOCAL) < amount_msat:
       -            raise PaymentFailure(f'Not enough local balance. Have: {self.available_to_spend(LOCAL)}, Need: {amount_msat}')
       -        if len(self.hm.htlcs(LOCAL)) + 1 > self.config[REMOTE].max_accepted_htlcs:
       -            raise PaymentFailure('Too many HTLCs already in channel')
       -        current_htlc_sum = htlcsum(self.hm.htlcs_by_direction(LOCAL, SENT)) + htlcsum(self.hm.htlcs_by_direction(LOCAL, RECEIVED))
       -        if current_htlc_sum + amount_msat > self.config[REMOTE].max_htlc_value_in_flight_msat:
       -            raise PaymentFailure(f'HTLC value sum (sum of pending htlcs: {current_htlc_sum/1000} sat plus new htlc: {amount_msat/1000} sat) would exceed max allowed: {self.config[REMOTE].max_htlc_value_in_flight_msat/1000} sat')
       -        if amount_msat < self.config[REMOTE].htlc_minimum_msat:
       -            raise PaymentFailure(f'HTLC value too small: {amount_msat} msat')
       -
       -    def can_pay(self, amount_msat):
       -        try:
       -            self._check_can_pay(amount_msat)
       -        except PaymentFailure:
       -            return False
       -        return True
       -
       -    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.
       -
       -        This docstring is from LND.
       -        """
       -        assert type(htlc) is dict
       -        self._check_can_pay(htlc['amount_msat'])
       -        htlc = UpdateAddHtlc(**htlc, htlc_id=self.config[LOCAL].next_htlc_id)
       -        self.hm.send_htlc(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.
       -
       -        This docstring is from LND.
       -        """
       -        assert type(htlc) is dict
       -        htlc = UpdateAddHtlc(**htlc, htlc_id = self.config[REMOTE].next_htlc_id)
       -        if self.available_to_spend(REMOTE) < htlc.amount_msat:
       -            raise RemoteMisbehaving('Remote dipped below channel reserve.' +\
       -                    f' Available at remote: {self.available_to_spend(REMOTE)},' +\
       -                    f' HTLC amount: {htlc.amount_msat}')
       -        self.hm.recv_htlc(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.
       -        The first return parameter is the signature for the commitment transaction
       -        itself, while the second parameter is are all HTLC signatures concatenated.
       -        any). The HTLC signatures are sorted according to the BIP 69 order of the
       -        HTLC's on the commitment transaction.
       -
       -        This docstring was adapted from LND.
       -        """
       -        self.print_error("sign_next_commitment")
       -
       -        self.hm.send_ctx()
       -
       -        pending_remote_commitment = self.pending_commitment(REMOTE)
       -        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 = []
       -        # they sent => we receive
       -        for we_receive, htlcs in zip([True, False], [self.included_htlcs(REMOTE, SENT, ctn=self.config[REMOTE].ctn+1), self.included_htlcs(REMOTE, RECEIVED, ctn=self.config[REMOTE].ctn+1)]):
       -            for htlc in htlcs:
       -                _script, htlc_tx = make_htlc_tx_with_open_channel(chan=self,
       -                                                                  pcp=self.config[REMOTE].next_per_commitment_point,
       -                                                                  for_us=for_us,
       -                                                                  we_receive=we_receive,
       -                                                                  commit=pending_remote_commitment,
       -                                                                  htlc=htlc)
       -                sig = bfh(htlc_tx.sign_txin(0, their_remote_htlc_privkey))
       -                htlc_sig = ecc.sig_string_from_der_sig(sig[:-1])
       -                htlc_output_idx = htlc_tx.inputs()[0]['prevout_n']
       -                htlcsigs.append((htlc_output_idx, htlc_sig))
       -
       -        htlcsigs.sort()
       -        htlcsigs = [x[1] for x in htlcsigs]
       -
       -        # TODO should add remote_commitment here and handle
       -        # both valid ctx'es in lnwatcher at the same time...
       -
       -        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.
       -
       -        This docstring is from LND.
       -        """
       -        self.print_error("receive_new_commitment")
       -
       -        self.hm.recv_ctx()
       -
       -        assert len(htlc_sigs) == 0 or type(htlc_sigs[0]) is bytes
       -
       -        pending_local_commitment = self.pending_commitment(LOCAL)
       -        preimage_hex = pending_local_commitment.serialize_preimage(0)
       -        pre_hash = sha256d(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)
       -
       -        htlc_sigs_string = b''.join(htlc_sigs)
       -
       -        htlc_sigs = htlc_sigs[:] # copy cause we will delete now
       -        ctn = self.config[LOCAL].ctn+1
       -        for htlcs, we_receive in [(self.included_htlcs(LOCAL, SENT, ctn=ctn), False), (self.included_htlcs(LOCAL, RECEIVED, ctn=ctn), True)]:
       -            for htlc in htlcs:
       -                idx = self.verify_htlc(htlc, htlc_sigs, we_receive, pending_local_commitment)
       -                del htlc_sigs[idx]
       -        if len(htlc_sigs) != 0: # all sigs should have been popped above
       -            raise Exception('failed verifying HTLC signatures: invalid amount of correct signatures')
       -
       -        self.config[LOCAL]=self.config[LOCAL]._replace(
       -            current_commitment_signature=sig,
       -            current_htlc_signatures=htlc_sigs_string,
       -            got_sig_for_next=True)
       -
       -        if self.pending_fee is not None:
       -            if not self.constraints.is_initiator:
       -                self.pending_fee[FUNDEE_SIGNED] = True
       -            if self.constraints.is_initiator and self.pending_fee[FUNDEE_ACKED]:
       -                self.pending_fee[FUNDER_SIGNED] = True
       -
       -        self.set_local_commitment(pending_local_commitment)
       -
       -    def verify_htlc(self, htlc: UpdateAddHtlc, htlc_sigs: Sequence[bytes], we_receive: bool, ctx) -> int:
       -        ctn = extract_ctn_from_tx_and_chan(ctx, self)
       -        secret = get_per_commitment_secret_from_seed(self.config[LOCAL].per_commitment_secret_seed, RevocationStore.START_INDEX - ctn)
       -        point = secret_to_pubkey(int.from_bytes(secret, 'big'))
       -
       -        _script, htlc_tx = make_htlc_tx_with_open_channel(chan=self,
       -                                                          pcp=point,
       -                                                          for_us=True,
       -                                                          we_receive=we_receive,
       -                                                          commit=ctx,
       -                                                          htlc=htlc)
       -        pre_hash = sha256d(bfh(htlc_tx.serialize_preimage(0)))
       -        remote_htlc_pubkey = derive_pubkey(self.config[REMOTE].htlc_basepoint.pubkey, point)
       -        for idx, sig in enumerate(htlc_sigs):
       -            if ecc.verify_signature(remote_htlc_pubkey, sig, pre_hash):
       -                return idx
       -        else:
       -            raise Exception(f'failed verifying HTLC signatures: {htlc}, sigs: {len(htlc_sigs)}, we_receive: {we_receive}')
       -
       -    def get_remote_htlc_sig_for_htlc(self, htlc: UpdateAddHtlc, we_receive: bool, ctx) -> bytes:
       -        data = self.config[LOCAL].current_htlc_signatures
       -        htlc_sigs = [data[i:i + 64] for i in range(0, len(data), 64)]
       -        idx = self.verify_htlc(htlc, htlc_sigs, we_receive=we_receive, ctx=ctx)
       -        remote_htlc_sig = ecc.der_sig_from_sig_string(htlc_sigs[idx]) + b'\x01'
       -        return remote_htlc_sig
       -
       -    def revoke_current_commitment(self):
       -        self.print_error("revoke_current_commitment")
       -
       -        last_secret, this_point, next_point, _ = self.points()
       -
       -        new_feerate = self.constraints.feerate
       -
       -        if self.pending_fee is not None:
       -            if not self.constraints.is_initiator and self.pending_fee[FUNDEE_SIGNED]:
       -                new_feerate = self.pending_fee.rate
       -                self.pending_fee = None
       -                print("FEERATE CHANGE COMPLETE (non-initiator)")
       -            if self.constraints.is_initiator and self.pending_fee[FUNDER_SIGNED]:
       -                new_feerate = self.pending_fee.rate
       -                self.pending_fee = None
       -                print("FEERATE CHANGE COMPLETE (initiator)")
       -
       -        assert self.config[LOCAL].got_sig_for_next
       -        self.constraints=self.constraints._replace(
       -            feerate=new_feerate
       -        )
       -        self.set_local_commitment(self.pending_commitment(LOCAL))
       -        ctx = self.pending_commitment(LOCAL)
       -        self.hm.send_rev()
       -        self.config[LOCAL]=self.config[LOCAL]._replace(
       -            ctn=self.config[LOCAL].ctn + 1,
       -            got_sig_for_next=False,
       -        )
       -        assert self.signature_fits(ctx)
       -
       -        return RevokeAndAck(last_secret, next_point), "current htlcs"
       -
       -    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'))
       -        last_point = secret_to_pubkey(int.from_bytes(last_secret, 'big'))
       -        return last_secret, this_point, next_point, last_point
       -
       -    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  # FIXME can't we just reconstruct it?
       -        sweeptxs = create_sweeptxs_for_their_just_revoked_ctx(self, ctx, per_commitment_secret, self.sweep_address)
       -        for prev_txid, tx in sweeptxs.items():
       -            if tx is not None:
       -                self.lnwatcher.add_sweep_tx(outpoint, prev_txid, tx.as_dict())
       -
       -    def receive_revocation(self, revocation) -> Tuple[int, int]:
       -        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_commitment(REMOTE)
       -
       -        self.config[REMOTE].revocation_store.add_next_entry(revocation.per_commitment_secret)
       -        self.process_new_revocation_secret(revocation.per_commitment_secret)
       -
       -        ##### start applying fee/htlc changes
       -
       -        if self.pending_fee is not None:
       -            if not self.constraints.is_initiator:
       -                self.pending_fee[FUNDEE_SIGNED] = True
       -            if self.constraints.is_initiator and self.pending_fee[FUNDEE_ACKED]:
       -                self.pending_fee[FUNDER_SIGNED] = True
       -
       -        received = self.hm.received_in_ctn(self.config[REMOTE].ctn + 1)
       -        sent = self.hm.sent_in_ctn(self.config[REMOTE].ctn + 1)
       -        for htlc in received:
       -            self.payment_completed(self, RECEIVED, htlc, None)
       -        for htlc in sent:
       -            preimage = self.preimages.pop(htlc.htlc_id)
       -            self.payment_completed(self, SENT, htlc, preimage)
       -        received_this_batch = htlcsum(received)
       -        sent_this_batch = htlcsum(sent)
       -
       -        next_point = self.config[REMOTE].next_per_commitment_point
       -
       -        self.hm.recv_rev()
       -
       -        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,
       -        )
       -
       -        if self.pending_fee is not None:
       -            if self.constraints.is_initiator:
       -                self.pending_fee[FUNDEE_ACKED] = True
       -
       -        self.set_remote_commitment()
       -        self.remote_commitment_to_be_revoked = prev_remote_commitment
       -
       -        return received_this_batch, sent_this_batch
       -
       -    def balance(self, subject, ctn=None):
       -        """
       -        This balance in mSAT is not including reserve and fees.
       -        So a node cannot actually use it's whole balance.
       -        But this number is simple, since it is derived simply
       -        from the initial balance, and the value of settled HTLCs.
       -        Note that it does not decrease once an HTLC is added,
       -        failed or fulfilled, since the balance change is only
       -        commited to later when the respective commitment
       -        transaction as been revoked.
       -        """
       -        assert type(subject) is HTLCOwner
       -        initial = self.config[subject].initial_msat
       -
       -        for direction, htlc in self.hm.settled_htlcs(subject, ctn):
       -            if direction == SENT:
       -                initial -= htlc.amount_msat
       -            else:
       -                initial += htlc.amount_msat
       -
       -        return initial
       -
       -    def balance_minus_outgoing_htlcs(self, subject):
       -        """
       -        This balance in mSAT, which includes the value of
       -        pending outgoing HTLCs, is used in the UI.
       -        """
       -        assert type(subject) is HTLCOwner
       -        ctn = self.hm.log[subject]['ctn'] + 1
       -        return self.balance(subject, ctn)\
       -                - htlcsum(self.hm.htlcs_by_direction(subject, SENT, ctn))
       -
       -    def available_to_spend(self, subject):
       -        """
       -        This balance in mSAT, while technically correct, can
       -        not be used in the UI cause it fluctuates (commit fee)
       -        """
       -        assert type(subject) is HTLCOwner
       -        return self.balance_minus_outgoing_htlcs(subject)\
       -                - self.config[-subject].reserve_sat * 1000\
       -                - calc_onchain_fees(
       -                      # TODO should we include a potential new htlc, when we are called from receive_htlc?
       -                      len(self.included_htlcs(subject, SENT) + self.included_htlcs(subject, RECEIVED)),
       -                      self.pending_feerate(subject),
       -                      self.constraints.is_initiator,
       -                  )[subject]
       -
       -    def included_htlcs(self, subject, direction, ctn=None):
       -        """
       -        return filter of non-dust htlcs for subjects commitment transaction, initiated by given party
       -        """
       -        assert type(subject) is HTLCOwner
       -        assert type(direction) is Direction
       -        if ctn is None:
       -            ctn = self.config[subject].ctn
       -        feerate = self.pending_feerate(subject)
       -        conf = self.config[subject]
       -        if (subject, direction) in [(REMOTE, RECEIVED), (LOCAL, SENT)]:
       -            weight = HTLC_SUCCESS_WEIGHT
       -        else:
       -            weight = HTLC_TIMEOUT_WEIGHT
       -        htlcs = self.hm.htlcs_by_direction(subject, direction, ctn=ctn)
       -        fee_for_htlc = lambda htlc: htlc.amount_msat // 1000 - (weight * feerate // 1000)
       -        return list(filter(lambda htlc: fee_for_htlc(htlc) >= conf.dust_limit_sat, htlcs))
       -
       -    def pending_feerate(self, subject):
       -        assert type(subject) is HTLCOwner
       -        candidate = self.constraints.feerate
       -        if self.pending_fee is not None:
       -            x = self.pending_fee.pending_feerate(subject)
       -            if x is not None:
       -                candidate = x
       -        return candidate
       -
       -    def pending_commitment(self, subject):
       -        assert type(subject) is HTLCOwner
       -        this_point = self.config[REMOTE].next_per_commitment_point if subject == REMOTE else self.points()[1]
       -        ctn = self.config[subject].ctn + 1
       -        feerate = self.pending_feerate(subject)
       -        return self.make_commitment(subject, this_point, ctn, feerate, True)
       -
       -    def current_commitment(self, subject):
       -        assert type(subject) is HTLCOwner
       -        this_point = self.config[REMOTE].current_per_commitment_point if subject == REMOTE else self.points()[3]
       -        ctn = self.config[subject].ctn
       -        feerate = self.constraints.feerate
       -        return self.make_commitment(subject, this_point, ctn, feerate, False)
       -
       -    def total_msat(self, direction):
       -        assert type(direction) is Direction
       -        sub = LOCAL if direction == SENT else REMOTE
       -        return htlcsum(self.hm.settled_htlcs_by(sub, self.config[sub].ctn))
       -
       -    def settle_htlc(self, preimage, htlc_id):
       -        """
       -        SettleHTLC attempts to settle an existing outstanding received HTLC.
       -        """
       -        self.print_error("settle_htlc")
       -        log = self.hm.log[REMOTE]
       -        htlc = log['adds'][htlc_id]
       -        assert htlc.payment_hash == sha256(preimage)
       -        assert htlc_id not in log['settles']
       -        self.hm.send_settle(htlc_id)
       -        # not saving preimage because it's already saved in LNWorker.invoices
       -
       -    def receive_htlc_settle(self, preimage, htlc_id):
       -        self.print_error("receive_htlc_settle")
       -        log = self.hm.log[LOCAL]
       -        htlc = log['adds'][htlc_id]
       -        assert htlc.payment_hash == sha256(preimage)
       -        assert htlc_id not in log['settles']
       -        self.hm.recv_settle(htlc_id)
       -        self.preimages[htlc_id] = preimage
       -        # we don't save the preimage because we don't need to forward it anyway
       -
       -    def fail_htlc(self, htlc_id):
       -        self.print_error("fail_htlc")
       -        self.hm.send_fail(htlc_id)
       -
       -    def receive_fail_htlc(self, htlc_id):
       -        self.print_error("receive_fail_htlc")
       -        self.hm.recv_fail(htlc_id)
       -
       -    @property
       -    def current_height(self):
       -        return {LOCAL: self.config[LOCAL].ctn, REMOTE: self.config[REMOTE].ctn}
       -
       -    def pending_local_fee(self):
       -        return self.constraints.capacity - sum(x[2] for x in self.pending_commitment(LOCAL).outputs())
       -
       -    def update_fee(self, feerate, initiator):
       -        if self.constraints.is_initiator != initiator:
       -            raise Exception("Cannot update_fee: wrong initiator", initiator)
       -        if self.pending_fee is not None:
       -            raise Exception("a fee update is already in progress")
       -        self.pending_fee = FeeUpdate(self, rate=feerate)
       -
       -    def to_save(self):
       -        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),
       -                "log": self.hm.to_save(),
       -                "onion_keys": str_bytes_dict_to_save(self.onion_keys),
       -                "force_closed": self.get_state() == 'FORCE_CLOSING',
       -        }
       -        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 = {}
       -        to_save_ref = self.to_save()
       -        for k, v in to_save_ref.items():
       -            if isinstance(v, tuple):
       -                serialized_channel[k] = namedtuples_to_dict(v)
       -            else:
       -                serialized_channel[k] = v
       -        dumped = ChannelJsonEncoder().encode(serialized_channel)
       -        roundtripped = json.loads(dumped)
       -        reconstructed = Channel(roundtripped)
       -        to_save_new = reconstructed.to_save()
       -        if to_save_new != to_save_ref:
       -            from pprint import PrettyPrinter
       -            pp = PrettyPrinter(indent=168)
       -            try:
       -                from deepdiff import DeepDiff
       -            except ImportError:
       -                raise Exception("Channels did not roundtrip serialization without changes:\n" + pp.pformat(to_save_ref) + "\n" + pp.pformat(to_save_new))
       -            else:
       -                raise Exception("Channels did not roundtrip serialization without changes:\n" + pp.pformat(DeepDiff(to_save_ref, to_save_new)))
       -        return roundtripped
       -
       -    def __str__(self):
       -        return str(self.serialize())
       -
       -    def make_commitment(self, subject, this_point, ctn, feerate, pending) -> Transaction:
       -        #if subject == REMOTE and not pending:
       -        #    ctn -= 1
       -        assert type(subject) is HTLCOwner
       -        other = REMOTE if LOCAL == subject else LOCAL
       -        remote_msat, local_msat = self.balance(other, ctn), self.balance(subject, ctn)
       -        received_htlcs = self.hm.htlcs_by_direction(subject, SENT if subject == LOCAL else RECEIVED, ctn)
       -        sent_htlcs = self.hm.htlcs_by_direction(subject, RECEIVED if subject == LOCAL else SENT, ctn)
       -        if subject != LOCAL:
       -            remote_msat -= htlcsum(received_htlcs)
       -            local_msat -= htlcsum(sent_htlcs)
       -        else:
       -            remote_msat -= htlcsum(sent_htlcs)
       -            local_msat -= htlcsum(received_htlcs)
       -        assert remote_msat >= 0
       -        assert local_msat >= 0
       -        # same htlcs as before, but now without dust.
       -        received_htlcs = self.included_htlcs(subject, SENT if subject == LOCAL else RECEIVED, ctn)
       -        sent_htlcs = self.included_htlcs(subject, RECEIVED if subject == LOCAL else SENT, ctn)
       -
       -        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 = []  # type: List[ScriptHtlc]
       -        for is_received_htlc, htlc_list in zip((subject != LOCAL, subject == LOCAL), (received_htlcs, sent_htlcs)):
       -            for htlc in htlc_list:
       -                htlcs.append(ScriptHtlc(make_htlc_output_witness_script(
       -                    is_received_htlc=is_received_htlc,
       -                    remote_revocation_pubkey=other_revocation_pubkey,
       -                    remote_htlc_pubkey=other_htlc_pubkey,
       -                    local_htlc_pubkey=this_htlc_pubkey,
       -                    payment_hash=htlc.payment_hash,
       -                    cltv_expiry=htlc.cltv_expiry), htlc))
       -        onchain_fees = calc_onchain_fees(
       -            len(htlcs),
       -            feerate,
       -            self.constraints.is_initiator == (subject == LOCAL),
       -        )
       -        payment_pubkey = derive_pubkey(other_config.payment_basepoint.pubkey, this_point)
       -        return make_commitment(
       -            ctn,
       -            this_config.multisig_key.pubkey,
       -            other_config.multisig_key.pubkey,
       -            payment_pubkey,
       -            self.config[LOCAL if     self.constraints.is_initiator else REMOTE].payment_basepoint.pubkey,
       -            self.config[LOCAL if not self.constraints.is_initiator else 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,
       -            onchain_fees,
       -            htlcs=htlcs)
       -
       -    def get_local_index(self):
       -        return int(self.config[LOCAL].multisig_key.pubkey < self.config[REMOTE].multisig_key.pubkey)
       -
       -    def make_closing_tx(self, local_script: bytes, remote_script: bytes,
       -                        fee_sat: int) -> Tuple[bytes, int, str]:
       -        """ cooperative close """
       -        _, outputs = make_commitment_outputs({
       -                    LOCAL:  fee_sat * 1000 if     self.constraints.is_initiator else 0,
       -                    REMOTE: fee_sat * 1000 if not self.constraints.is_initiator else 0,
       -                },
       -                self.balance(LOCAL),
       -                self.balance(REMOTE),
       -                (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,
       -                                     funding_txid=self.funding_outpoint.txid,
       -                                     funding_pos=self.funding_outpoint.output_index,
       -                                     funding_sat=self.constraints.capacity,
       -                                     outputs=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, closing_tx
       -
       -    def signature_fits(self, tx):
       -        remote_sig = self.config[LOCAL].current_commitment_signature
       -        preimage_hex = tx.serialize_preimage(0)
       -        pre_hash = sha256d(bfh(preimage_hex))
       -        assert remote_sig
       -        res = ecc.verify_signature(self.config[REMOTE].multisig_key.pubkey, remote_sig, pre_hash)
       -        return res
       -
       -    def force_close_tx(self):
       -        tx = self.local_commitment
       -        assert self.signature_fits(tx)
       -        tx = Transaction(str(tx))
       -        tx.deserialize(True)
       -        tx.sign({bh2u(self.config[LOCAL].multisig_key.pubkey): (self.config[LOCAL].multisig_key.privkey, True)})
       -        remote_sig = self.config[LOCAL].current_commitment_signature
       -        remote_sig = ecc.der_sig_from_sig_string(remote_sig) + b"\x01"
       -        sigs = tx._inputs[0]["signatures"]
       -        none_idx = sigs.index(None)
       -        tx.add_signature_to_txin(0, none_idx, bh2u(remote_sig))
       -        assert tx.is_complete()
       -        return tx
       -
       -    def included_htlcs_in_their_latest_ctxs(self, htlc_initiator) -> Dict[int, List[UpdateAddHtlc]]:
       -        """ A map from commitment number to list of HTLCs in
       -            their latest two commitment transactions.
       -            The oldest might have been revoked.  """
       -        assert type(htlc_initiator) is HTLCOwner
       -        direction = RECEIVED if htlc_initiator == LOCAL else SENT
       -        old_ctn = self.config[REMOTE].ctn
       -        old_htlcs  = self.included_htlcs(REMOTE, direction, ctn=old_ctn)
       -
       -        new_ctn = self.config[REMOTE].ctn+1
       -        new_htlcs  = self.included_htlcs(REMOTE, direction, ctn=new_ctn)
       -
       -        return {old_ctn: old_htlcs,
       -                new_ctn: new_htlcs, }
   DIR diff --git a/electrum/lnchannel.py b/electrum/lnchannel.py
       t@@ -0,0 +1,810 @@
       +# Copyright (C) 2018 The Electrum developers
       +# Copyright (C) 2015-2018 The Lightning Network Developers
       +#
       +# Permission is hereby granted, free of charge, to any person obtaining a copy
       +# of this software and associated documentation files (the "Software"), to deal
       +# in the Software without restriction, including without limitation the rights
       +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
       +# copies of the Software, and to permit persons to whom the Software is
       +# furnished to do so, subject to the following conditions:
       +#
       +# The above copyright notice and this permission notice shall be included in
       +# all copies or substantial portions of the Software.
       +#
       +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
       +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
       +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
       +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
       +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
       +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
       +# THE SOFTWARE.
       +
       +# API (method signatures and docstrings) partially copied from lnd
       +# 42de4400bff5105352d0552155f73589166d162b
       +
       +import os
       +from collections import namedtuple, defaultdict
       +import binascii
       +import json
       +from enum import Enum, auto
       +from typing import Optional, Dict, List, Tuple, NamedTuple, Set, Callable, Iterable, Sequence
       +
       +from . import ecc
       +from .util import bfh, PrintError, bh2u
       +from .bitcoin import TYPE_SCRIPT, TYPE_ADDRESS
       +from .bitcoin import redeem_script_to_address
       +from .crypto import sha256, sha256d
       +from .simple_config import get_config
       +from .transaction import Transaction
       +
       +from .lnutil import (Outpoint, LocalConfig, RemoteConfig, Keypair, OnlyPubkeyKeypair, ChannelConstraints,
       +                    get_per_commitment_secret_from_seed, secret_to_pubkey, derive_privkey, make_closing_tx,
       +                    sign_and_get_sig_string, RevocationStore, derive_blinded_pubkey, Direction, derive_pubkey,
       +                    make_htlc_tx_with_open_channel, make_commitment, make_received_htlc, make_offered_htlc,
       +                    HTLC_TIMEOUT_WEIGHT, HTLC_SUCCESS_WEIGHT, extract_ctn_from_tx_and_chan, UpdateAddHtlc,
       +                    funding_output_script, SENT, RECEIVED, LOCAL, REMOTE, HTLCOwner, make_commitment_outputs,
       +                    ScriptHtlc, PaymentFailure, calc_onchain_fees, RemoteMisbehaving, make_htlc_output_witness_script)
       +from .lnsweep import create_sweeptxs_for_their_just_revoked_ctx
       +from .lnsweep import create_sweeptxs_for_our_latest_ctx, create_sweeptxs_for_their_latest_ctx
       +from .lnhtlc import HTLCManager
       +
       +
       +class ChannelJsonEncoder(json.JSONEncoder):
       +    def default(self, o):
       +        if isinstance(o, bytes):
       +            return binascii.hexlify(o).decode("ascii")
       +        if isinstance(o, RevocationStore):
       +            return o.serialize()
       +        if isinstance(o, set):
       +            return list(o)
       +        return super().default(o)
       +
       +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
       +
       +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])
       +
       +# following two functions are used because json
       +# doesn't store int keys and byte string values
       +def str_bytes_dict_from_save(x):
       +    return {int(k): bfh(v) for k,v in x.items()}
       +
       +def str_bytes_dict_to_save(x):
       +    return {str(k): bh2u(v) for k, v in x.items()}
       +
       +class Channel(PrintError):
       +    def diagnostic_name(self):
       +        if self.name:
       +            return str(self.name)
       +        try:
       +            return f"lnchannel_{bh2u(self.channel_id[-4:])}"
       +        except:
       +            return super().diagnostic_name()
       +
       +    def __init__(self, state, sweep_address = None, name = None, payment_completed : Optional[Callable[[Direction, UpdateAddHtlc, bytes], None]] = None):
       +        self.preimages = {}
       +        if not payment_completed:
       +            payment_completed = lambda this, x, y, z: None
       +        self.sweep_address = sweep_address
       +        self.payment_completed = payment_completed
       +        assert 'local_state' not in state
       +        self.config = {}
       +        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 = str_bytes_dict_from_save(state.get('onion_keys', {}))
       +
       +        # 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"])
       +        self.remote_commitment_to_be_revoked.deserialize(True)
       +
       +        self.hm = HTLCManager(state.get('log'))
       +
       +        self.name = name
       +
       +        self.pending_fee = None
       +
       +        self._is_funding_txo_spent = None  # "don't know"
       +        self._state = None
       +        if state.get('force_closed', False):
       +            self.set_state('FORCE_CLOSING')
       +        else:
       +            self.set_state('DISCONNECTED')
       +
       +        self.lnwatcher = None
       +
       +        self.local_commitment = None
       +        self.remote_commitment = None
       +
       +    def get_payments(self):
       +        out = {}
       +        for subject in LOCAL, REMOTE:
       +            log = self.hm.log[subject]
       +            for htlc_id, htlc in log.get('adds', {}).items():
       +                rhash = bh2u(htlc.payment_hash)
       +                status = 'settled' if htlc_id in log.get('settles',{}) else 'inflight'
       +                direction = SENT if subject is LOCAL else RECEIVED
       +                out[rhash] = (self.channel_id, htlc, direction, status)
       +        return out
       +
       +    def set_local_commitment(self, ctx):
       +        ctn = extract_ctn_from_tx_and_chan(ctx, self)
       +        assert self.signature_fits(ctx), (self.log[LOCAL])
       +        self.local_commitment = ctx
       +        if self.sweep_address is not None:
       +            self.local_sweeptxs = create_sweeptxs_for_our_latest_ctx(self, self.local_commitment, self.sweep_address)
       +            initial = os.path.join(get_config().electrum_path(), 'initial_commitment_tx')
       +            tx = self.force_close_tx().serialize_to_network()
       +            if not os.path.exists(initial):
       +                with open(initial, 'w') as f:
       +                    f.write(tx)
       +
       +    def set_remote_commitment(self):
       +        self.remote_commitment = self.current_commitment(REMOTE)
       +        if self.sweep_address is not None:
       +            self.remote_sweeptxs = create_sweeptxs_for_their_latest_ctx(self, self.remote_commitment, self.sweep_address)
       +
       +    def set_state(self, state: str):
       +        if self._state == 'FORCE_CLOSING':
       +            assert state == 'FORCE_CLOSING', 'new state was not FORCE_CLOSING: ' + state
       +        self._state = state
       +
       +    def get_state(self):
       +        return self._state
       +
       +    def is_closed(self):
       +        return self.get_state() in ['CLOSED', 'FORCE_CLOSING']
       +
       +    def _check_can_pay(self, amount_msat: int) -> None:
       +        if self.get_state() != 'OPEN':
       +            raise PaymentFailure('Channel not open')
       +        if self.available_to_spend(LOCAL) < amount_msat:
       +            raise PaymentFailure(f'Not enough local balance. Have: {self.available_to_spend(LOCAL)}, Need: {amount_msat}')
       +        if len(self.hm.htlcs(LOCAL)) + 1 > self.config[REMOTE].max_accepted_htlcs:
       +            raise PaymentFailure('Too many HTLCs already in channel')
       +        current_htlc_sum = htlcsum(self.hm.htlcs_by_direction(LOCAL, SENT)) + htlcsum(self.hm.htlcs_by_direction(LOCAL, RECEIVED))
       +        if current_htlc_sum + amount_msat > self.config[REMOTE].max_htlc_value_in_flight_msat:
       +            raise PaymentFailure(f'HTLC value sum (sum of pending htlcs: {current_htlc_sum/1000} sat plus new htlc: {amount_msat/1000} sat) would exceed max allowed: {self.config[REMOTE].max_htlc_value_in_flight_msat/1000} sat')
       +        if amount_msat < self.config[REMOTE].htlc_minimum_msat:
       +            raise PaymentFailure(f'HTLC value too small: {amount_msat} msat')
       +
       +    def can_pay(self, amount_msat):
       +        try:
       +            self._check_can_pay(amount_msat)
       +        except PaymentFailure:
       +            return False
       +        return True
       +
       +    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.
       +
       +        This docstring is from LND.
       +        """
       +        assert type(htlc) is dict
       +        self._check_can_pay(htlc['amount_msat'])
       +        htlc = UpdateAddHtlc(**htlc, htlc_id=self.config[LOCAL].next_htlc_id)
       +        self.hm.send_htlc(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.
       +
       +        This docstring is from LND.
       +        """
       +        assert type(htlc) is dict
       +        htlc = UpdateAddHtlc(**htlc, htlc_id = self.config[REMOTE].next_htlc_id)
       +        if self.available_to_spend(REMOTE) < htlc.amount_msat:
       +            raise RemoteMisbehaving('Remote dipped below channel reserve.' +\
       +                    f' Available at remote: {self.available_to_spend(REMOTE)},' +\
       +                    f' HTLC amount: {htlc.amount_msat}')
       +        self.hm.recv_htlc(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.
       +        The first return parameter is the signature for the commitment transaction
       +        itself, while the second parameter is are all HTLC signatures concatenated.
       +        any). The HTLC signatures are sorted according to the BIP 69 order of the
       +        HTLC's on the commitment transaction.
       +
       +        This docstring was adapted from LND.
       +        """
       +        self.print_error("sign_next_commitment")
       +
       +        self.hm.send_ctx()
       +
       +        pending_remote_commitment = self.pending_commitment(REMOTE)
       +        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 = []
       +        # they sent => we receive
       +        for we_receive, htlcs in zip([True, False], [self.included_htlcs(REMOTE, SENT, ctn=self.config[REMOTE].ctn+1), self.included_htlcs(REMOTE, RECEIVED, ctn=self.config[REMOTE].ctn+1)]):
       +            for htlc in htlcs:
       +                _script, htlc_tx = make_htlc_tx_with_open_channel(chan=self,
       +                                                                  pcp=self.config[REMOTE].next_per_commitment_point,
       +                                                                  for_us=for_us,
       +                                                                  we_receive=we_receive,
       +                                                                  commit=pending_remote_commitment,
       +                                                                  htlc=htlc)
       +                sig = bfh(htlc_tx.sign_txin(0, their_remote_htlc_privkey))
       +                htlc_sig = ecc.sig_string_from_der_sig(sig[:-1])
       +                htlc_output_idx = htlc_tx.inputs()[0]['prevout_n']
       +                htlcsigs.append((htlc_output_idx, htlc_sig))
       +
       +        htlcsigs.sort()
       +        htlcsigs = [x[1] for x in htlcsigs]
       +
       +        # TODO should add remote_commitment here and handle
       +        # both valid ctx'es in lnwatcher at the same time...
       +
       +        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.
       +
       +        This docstring is from LND.
       +        """
       +        self.print_error("receive_new_commitment")
       +
       +        self.hm.recv_ctx()
       +
       +        assert len(htlc_sigs) == 0 or type(htlc_sigs[0]) is bytes
       +
       +        pending_local_commitment = self.pending_commitment(LOCAL)
       +        preimage_hex = pending_local_commitment.serialize_preimage(0)
       +        pre_hash = sha256d(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)
       +
       +        htlc_sigs_string = b''.join(htlc_sigs)
       +
       +        htlc_sigs = htlc_sigs[:] # copy cause we will delete now
       +        ctn = self.config[LOCAL].ctn+1
       +        for htlcs, we_receive in [(self.included_htlcs(LOCAL, SENT, ctn=ctn), False), (self.included_htlcs(LOCAL, RECEIVED, ctn=ctn), True)]:
       +            for htlc in htlcs:
       +                idx = self.verify_htlc(htlc, htlc_sigs, we_receive, pending_local_commitment)
       +                del htlc_sigs[idx]
       +        if len(htlc_sigs) != 0: # all sigs should have been popped above
       +            raise Exception('failed verifying HTLC signatures: invalid amount of correct signatures')
       +
       +        self.config[LOCAL]=self.config[LOCAL]._replace(
       +            current_commitment_signature=sig,
       +            current_htlc_signatures=htlc_sigs_string,
       +            got_sig_for_next=True)
       +
       +        if self.pending_fee is not None:
       +            if not self.constraints.is_initiator:
       +                self.pending_fee[FUNDEE_SIGNED] = True
       +            if self.constraints.is_initiator and self.pending_fee[FUNDEE_ACKED]:
       +                self.pending_fee[FUNDER_SIGNED] = True
       +
       +        self.set_local_commitment(pending_local_commitment)
       +
       +    def verify_htlc(self, htlc: UpdateAddHtlc, htlc_sigs: Sequence[bytes], we_receive: bool, ctx) -> int:
       +        ctn = extract_ctn_from_tx_and_chan(ctx, self)
       +        secret = get_per_commitment_secret_from_seed(self.config[LOCAL].per_commitment_secret_seed, RevocationStore.START_INDEX - ctn)
       +        point = secret_to_pubkey(int.from_bytes(secret, 'big'))
       +
       +        _script, htlc_tx = make_htlc_tx_with_open_channel(chan=self,
       +                                                          pcp=point,
       +                                                          for_us=True,
       +                                                          we_receive=we_receive,
       +                                                          commit=ctx,
       +                                                          htlc=htlc)
       +        pre_hash = sha256d(bfh(htlc_tx.serialize_preimage(0)))
       +        remote_htlc_pubkey = derive_pubkey(self.config[REMOTE].htlc_basepoint.pubkey, point)
       +        for idx, sig in enumerate(htlc_sigs):
       +            if ecc.verify_signature(remote_htlc_pubkey, sig, pre_hash):
       +                return idx
       +        else:
       +            raise Exception(f'failed verifying HTLC signatures: {htlc}, sigs: {len(htlc_sigs)}, we_receive: {we_receive}')
       +
       +    def get_remote_htlc_sig_for_htlc(self, htlc: UpdateAddHtlc, we_receive: bool, ctx) -> bytes:
       +        data = self.config[LOCAL].current_htlc_signatures
       +        htlc_sigs = [data[i:i + 64] for i in range(0, len(data), 64)]
       +        idx = self.verify_htlc(htlc, htlc_sigs, we_receive=we_receive, ctx=ctx)
       +        remote_htlc_sig = ecc.der_sig_from_sig_string(htlc_sigs[idx]) + b'\x01'
       +        return remote_htlc_sig
       +
       +    def revoke_current_commitment(self):
       +        self.print_error("revoke_current_commitment")
       +
       +        last_secret, this_point, next_point, _ = self.points()
       +
       +        new_feerate = self.constraints.feerate
       +
       +        if self.pending_fee is not None:
       +            if not self.constraints.is_initiator and self.pending_fee[FUNDEE_SIGNED]:
       +                new_feerate = self.pending_fee.rate
       +                self.pending_fee = None
       +                print("FEERATE CHANGE COMPLETE (non-initiator)")
       +            if self.constraints.is_initiator and self.pending_fee[FUNDER_SIGNED]:
       +                new_feerate = self.pending_fee.rate
       +                self.pending_fee = None
       +                print("FEERATE CHANGE COMPLETE (initiator)")
       +
       +        assert self.config[LOCAL].got_sig_for_next
       +        self.constraints=self.constraints._replace(
       +            feerate=new_feerate
       +        )
       +        self.set_local_commitment(self.pending_commitment(LOCAL))
       +        ctx = self.pending_commitment(LOCAL)
       +        self.hm.send_rev()
       +        self.config[LOCAL]=self.config[LOCAL]._replace(
       +            ctn=self.config[LOCAL].ctn + 1,
       +            got_sig_for_next=False,
       +        )
       +        assert self.signature_fits(ctx)
       +
       +        return RevokeAndAck(last_secret, next_point), "current htlcs"
       +
       +    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'))
       +        last_point = secret_to_pubkey(int.from_bytes(last_secret, 'big'))
       +        return last_secret, this_point, next_point, last_point
       +
       +    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  # FIXME can't we just reconstruct it?
       +        sweeptxs = create_sweeptxs_for_their_just_revoked_ctx(self, ctx, per_commitment_secret, self.sweep_address)
       +        for prev_txid, tx in sweeptxs.items():
       +            if tx is not None:
       +                self.lnwatcher.add_sweep_tx(outpoint, prev_txid, tx.as_dict())
       +
       +    def receive_revocation(self, revocation) -> Tuple[int, int]:
       +        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_commitment(REMOTE)
       +
       +        self.config[REMOTE].revocation_store.add_next_entry(revocation.per_commitment_secret)
       +        self.process_new_revocation_secret(revocation.per_commitment_secret)
       +
       +        ##### start applying fee/htlc changes
       +
       +        if self.pending_fee is not None:
       +            if not self.constraints.is_initiator:
       +                self.pending_fee[FUNDEE_SIGNED] = True
       +            if self.constraints.is_initiator and self.pending_fee[FUNDEE_ACKED]:
       +                self.pending_fee[FUNDER_SIGNED] = True
       +
       +        received = self.hm.received_in_ctn(self.config[REMOTE].ctn + 1)
       +        sent = self.hm.sent_in_ctn(self.config[REMOTE].ctn + 1)
       +        for htlc in received:
       +            self.payment_completed(self, RECEIVED, htlc, None)
       +        for htlc in sent:
       +            preimage = self.preimages.pop(htlc.htlc_id)
       +            self.payment_completed(self, SENT, htlc, preimage)
       +        received_this_batch = htlcsum(received)
       +        sent_this_batch = htlcsum(sent)
       +
       +        next_point = self.config[REMOTE].next_per_commitment_point
       +
       +        self.hm.recv_rev()
       +
       +        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,
       +        )
       +
       +        if self.pending_fee is not None:
       +            if self.constraints.is_initiator:
       +                self.pending_fee[FUNDEE_ACKED] = True
       +
       +        self.set_remote_commitment()
       +        self.remote_commitment_to_be_revoked = prev_remote_commitment
       +
       +        return received_this_batch, sent_this_batch
       +
       +    def balance(self, subject, ctn=None):
       +        """
       +        This balance in mSAT is not including reserve and fees.
       +        So a node cannot actually use it's whole balance.
       +        But this number is simple, since it is derived simply
       +        from the initial balance, and the value of settled HTLCs.
       +        Note that it does not decrease once an HTLC is added,
       +        failed or fulfilled, since the balance change is only
       +        commited to later when the respective commitment
       +        transaction as been revoked.
       +        """
       +        assert type(subject) is HTLCOwner
       +        initial = self.config[subject].initial_msat
       +
       +        for direction, htlc in self.hm.settled_htlcs(subject, ctn):
       +            if direction == SENT:
       +                initial -= htlc.amount_msat
       +            else:
       +                initial += htlc.amount_msat
       +
       +        return initial
       +
       +    def balance_minus_outgoing_htlcs(self, subject):
       +        """
       +        This balance in mSAT, which includes the value of
       +        pending outgoing HTLCs, is used in the UI.
       +        """
       +        assert type(subject) is HTLCOwner
       +        ctn = self.hm.log[subject]['ctn'] + 1
       +        return self.balance(subject, ctn)\
       +                - htlcsum(self.hm.htlcs_by_direction(subject, SENT, ctn))
       +
       +    def available_to_spend(self, subject):
       +        """
       +        This balance in mSAT, while technically correct, can
       +        not be used in the UI cause it fluctuates (commit fee)
       +        """
       +        assert type(subject) is HTLCOwner
       +        return self.balance_minus_outgoing_htlcs(subject)\
       +                - self.config[-subject].reserve_sat * 1000\
       +                - calc_onchain_fees(
       +                      # TODO should we include a potential new htlc, when we are called from receive_htlc?
       +                      len(self.included_htlcs(subject, SENT) + self.included_htlcs(subject, RECEIVED)),
       +                      self.pending_feerate(subject),
       +                      self.constraints.is_initiator,
       +                  )[subject]
       +
       +    def included_htlcs(self, subject, direction, ctn=None):
       +        """
       +        return filter of non-dust htlcs for subjects commitment transaction, initiated by given party
       +        """
       +        assert type(subject) is HTLCOwner
       +        assert type(direction) is Direction
       +        if ctn is None:
       +            ctn = self.config[subject].ctn
       +        feerate = self.pending_feerate(subject)
       +        conf = self.config[subject]
       +        if (subject, direction) in [(REMOTE, RECEIVED), (LOCAL, SENT)]:
       +            weight = HTLC_SUCCESS_WEIGHT
       +        else:
       +            weight = HTLC_TIMEOUT_WEIGHT
       +        htlcs = self.hm.htlcs_by_direction(subject, direction, ctn=ctn)
       +        fee_for_htlc = lambda htlc: htlc.amount_msat // 1000 - (weight * feerate // 1000)
       +        return list(filter(lambda htlc: fee_for_htlc(htlc) >= conf.dust_limit_sat, htlcs))
       +
       +    def pending_feerate(self, subject):
       +        assert type(subject) is HTLCOwner
       +        candidate = self.constraints.feerate
       +        if self.pending_fee is not None:
       +            x = self.pending_fee.pending_feerate(subject)
       +            if x is not None:
       +                candidate = x
       +        return candidate
       +
       +    def pending_commitment(self, subject):
       +        assert type(subject) is HTLCOwner
       +        this_point = self.config[REMOTE].next_per_commitment_point if subject == REMOTE else self.points()[1]
       +        ctn = self.config[subject].ctn + 1
       +        feerate = self.pending_feerate(subject)
       +        return self.make_commitment(subject, this_point, ctn, feerate, True)
       +
       +    def current_commitment(self, subject):
       +        assert type(subject) is HTLCOwner
       +        this_point = self.config[REMOTE].current_per_commitment_point if subject == REMOTE else self.points()[3]
       +        ctn = self.config[subject].ctn
       +        feerate = self.constraints.feerate
       +        return self.make_commitment(subject, this_point, ctn, feerate, False)
       +
       +    def total_msat(self, direction):
       +        assert type(direction) is Direction
       +        sub = LOCAL if direction == SENT else REMOTE
       +        return htlcsum(self.hm.settled_htlcs_by(sub, self.config[sub].ctn))
       +
       +    def settle_htlc(self, preimage, htlc_id):
       +        """
       +        SettleHTLC attempts to settle an existing outstanding received HTLC.
       +        """
       +        self.print_error("settle_htlc")
       +        log = self.hm.log[REMOTE]
       +        htlc = log['adds'][htlc_id]
       +        assert htlc.payment_hash == sha256(preimage)
       +        assert htlc_id not in log['settles']
       +        self.hm.send_settle(htlc_id)
       +        # not saving preimage because it's already saved in LNWorker.invoices
       +
       +    def receive_htlc_settle(self, preimage, htlc_id):
       +        self.print_error("receive_htlc_settle")
       +        log = self.hm.log[LOCAL]
       +        htlc = log['adds'][htlc_id]
       +        assert htlc.payment_hash == sha256(preimage)
       +        assert htlc_id not in log['settles']
       +        self.hm.recv_settle(htlc_id)
       +        self.preimages[htlc_id] = preimage
       +        # we don't save the preimage because we don't need to forward it anyway
       +
       +    def fail_htlc(self, htlc_id):
       +        self.print_error("fail_htlc")
       +        self.hm.send_fail(htlc_id)
       +
       +    def receive_fail_htlc(self, htlc_id):
       +        self.print_error("receive_fail_htlc")
       +        self.hm.recv_fail(htlc_id)
       +
       +    @property
       +    def current_height(self):
       +        return {LOCAL: self.config[LOCAL].ctn, REMOTE: self.config[REMOTE].ctn}
       +
       +    def pending_local_fee(self):
       +        return self.constraints.capacity - sum(x[2] for x in self.pending_commitment(LOCAL).outputs())
       +
       +    def update_fee(self, feerate, initiator):
       +        if self.constraints.is_initiator != initiator:
       +            raise Exception("Cannot update_fee: wrong initiator", initiator)
       +        if self.pending_fee is not None:
       +            raise Exception("a fee update is already in progress")
       +        self.pending_fee = FeeUpdate(self, rate=feerate)
       +
       +    def to_save(self):
       +        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),
       +                "log": self.hm.to_save(),
       +                "onion_keys": str_bytes_dict_to_save(self.onion_keys),
       +                "force_closed": self.get_state() == 'FORCE_CLOSING',
       +        }
       +        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 = {}
       +        to_save_ref = self.to_save()
       +        for k, v in to_save_ref.items():
       +            if isinstance(v, tuple):
       +                serialized_channel[k] = namedtuples_to_dict(v)
       +            else:
       +                serialized_channel[k] = v
       +        dumped = ChannelJsonEncoder().encode(serialized_channel)
       +        roundtripped = json.loads(dumped)
       +        reconstructed = Channel(roundtripped)
       +        to_save_new = reconstructed.to_save()
       +        if to_save_new != to_save_ref:
       +            from pprint import PrettyPrinter
       +            pp = PrettyPrinter(indent=168)
       +            try:
       +                from deepdiff import DeepDiff
       +            except ImportError:
       +                raise Exception("Channels did not roundtrip serialization without changes:\n" + pp.pformat(to_save_ref) + "\n" + pp.pformat(to_save_new))
       +            else:
       +                raise Exception("Channels did not roundtrip serialization without changes:\n" + pp.pformat(DeepDiff(to_save_ref, to_save_new)))
       +        return roundtripped
       +
       +    def __str__(self):
       +        return str(self.serialize())
       +
       +    def make_commitment(self, subject, this_point, ctn, feerate, pending) -> Transaction:
       +        #if subject == REMOTE and not pending:
       +        #    ctn -= 1
       +        assert type(subject) is HTLCOwner
       +        other = REMOTE if LOCAL == subject else LOCAL
       +        remote_msat, local_msat = self.balance(other, ctn), self.balance(subject, ctn)
       +        received_htlcs = self.hm.htlcs_by_direction(subject, SENT if subject == LOCAL else RECEIVED, ctn)
       +        sent_htlcs = self.hm.htlcs_by_direction(subject, RECEIVED if subject == LOCAL else SENT, ctn)
       +        if subject != LOCAL:
       +            remote_msat -= htlcsum(received_htlcs)
       +            local_msat -= htlcsum(sent_htlcs)
       +        else:
       +            remote_msat -= htlcsum(sent_htlcs)
       +            local_msat -= htlcsum(received_htlcs)
       +        assert remote_msat >= 0
       +        assert local_msat >= 0
       +        # same htlcs as before, but now without dust.
       +        received_htlcs = self.included_htlcs(subject, SENT if subject == LOCAL else RECEIVED, ctn)
       +        sent_htlcs = self.included_htlcs(subject, RECEIVED if subject == LOCAL else SENT, ctn)
       +
       +        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 = []  # type: List[ScriptHtlc]
       +        for is_received_htlc, htlc_list in zip((subject != LOCAL, subject == LOCAL), (received_htlcs, sent_htlcs)):
       +            for htlc in htlc_list:
       +                htlcs.append(ScriptHtlc(make_htlc_output_witness_script(
       +                    is_received_htlc=is_received_htlc,
       +                    remote_revocation_pubkey=other_revocation_pubkey,
       +                    remote_htlc_pubkey=other_htlc_pubkey,
       +                    local_htlc_pubkey=this_htlc_pubkey,
       +                    payment_hash=htlc.payment_hash,
       +                    cltv_expiry=htlc.cltv_expiry), htlc))
       +        onchain_fees = calc_onchain_fees(
       +            len(htlcs),
       +            feerate,
       +            self.constraints.is_initiator == (subject == LOCAL),
       +        )
       +        payment_pubkey = derive_pubkey(other_config.payment_basepoint.pubkey, this_point)
       +        return make_commitment(
       +            ctn,
       +            this_config.multisig_key.pubkey,
       +            other_config.multisig_key.pubkey,
       +            payment_pubkey,
       +            self.config[LOCAL if     self.constraints.is_initiator else REMOTE].payment_basepoint.pubkey,
       +            self.config[LOCAL if not self.constraints.is_initiator else 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,
       +            onchain_fees,
       +            htlcs=htlcs)
       +
       +    def get_local_index(self):
       +        return int(self.config[LOCAL].multisig_key.pubkey < self.config[REMOTE].multisig_key.pubkey)
       +
       +    def make_closing_tx(self, local_script: bytes, remote_script: bytes,
       +                        fee_sat: int) -> Tuple[bytes, int, str]:
       +        """ cooperative close """
       +        _, outputs = make_commitment_outputs({
       +                    LOCAL:  fee_sat * 1000 if     self.constraints.is_initiator else 0,
       +                    REMOTE: fee_sat * 1000 if not self.constraints.is_initiator else 0,
       +                },
       +                self.balance(LOCAL),
       +                self.balance(REMOTE),
       +                (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,
       +                                     funding_txid=self.funding_outpoint.txid,
       +                                     funding_pos=self.funding_outpoint.output_index,
       +                                     funding_sat=self.constraints.capacity,
       +                                     outputs=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, closing_tx
       +
       +    def signature_fits(self, tx):
       +        remote_sig = self.config[LOCAL].current_commitment_signature
       +        preimage_hex = tx.serialize_preimage(0)
       +        pre_hash = sha256d(bfh(preimage_hex))
       +        assert remote_sig
       +        res = ecc.verify_signature(self.config[REMOTE].multisig_key.pubkey, remote_sig, pre_hash)
       +        return res
       +
       +    def force_close_tx(self):
       +        tx = self.local_commitment
       +        assert self.signature_fits(tx)
       +        tx = Transaction(str(tx))
       +        tx.deserialize(True)
       +        tx.sign({bh2u(self.config[LOCAL].multisig_key.pubkey): (self.config[LOCAL].multisig_key.privkey, True)})
       +        remote_sig = self.config[LOCAL].current_commitment_signature
       +        remote_sig = ecc.der_sig_from_sig_string(remote_sig) + b"\x01"
       +        sigs = tx._inputs[0]["signatures"]
       +        none_idx = sigs.index(None)
       +        tx.add_signature_to_txin(0, none_idx, bh2u(remote_sig))
       +        assert tx.is_complete()
       +        return tx
       +
       +    def included_htlcs_in_their_latest_ctxs(self, htlc_initiator) -> Dict[int, List[UpdateAddHtlc]]:
       +        """ A map from commitment number to list of HTLCs in
       +            their latest two commitment transactions.
       +            The oldest might have been revoked.  """
       +        assert type(htlc_initiator) is HTLCOwner
       +        direction = RECEIVED if htlc_initiator == LOCAL else SENT
       +        old_ctn = self.config[REMOTE].ctn
       +        old_htlcs  = self.included_htlcs(REMOTE, direction, ctn=old_ctn)
       +
       +        new_ctn = self.config[REMOTE].ctn+1
       +        new_htlcs  = self.included_htlcs(REMOTE, direction, ctn=new_ctn)
       +
       +        return {old_ctn: old_htlcs,
       +                new_ctn: new_htlcs, }
   DIR diff --git a/electrum/lnchannelverifier.py b/electrum/lnchannelverifier.py
       t@@ -1,206 +0,0 @@
       -# -*- coding: utf-8 -*-
       -#
       -# Electrum - lightweight Bitcoin client
       -# Copyright (C) 2018 The Electrum developers
       -#
       -# Permission is hereby granted, free of charge, to any person
       -# obtaining a copy of this software and associated documentation files
       -# (the "Software"), to deal in the Software without restriction,
       -# including without limitation the rights to use, copy, modify, merge,
       -# publish, distribute, sublicense, and/or sell copies of the Software,
       -# and to permit persons to whom the Software is furnished to do so,
       -# subject to the following conditions:
       -#
       -# The above copyright notice and this permission notice shall be
       -# included in all copies or substantial portions of the Software.
       -#
       -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
       -# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
       -# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
       -# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
       -# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
       -# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
       -# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
       -# SOFTWARE.
       -
       -import asyncio
       -import threading
       -from typing import TYPE_CHECKING
       -
       -import aiorpcx
       -
       -from . import lnbase
       -from . import bitcoin
       -from . import ecc
       -from . import constants
       -from .util import bh2u, bfh, NetworkJobOnDefaultServer
       -from .lnutil import invert_short_channel_id, funding_output_script_from_keys
       -from .verifier import verify_tx_is_in_block, MerkleVerificationFailure
       -from .transaction import Transaction
       -from .interface import GracefulDisconnect
       -from .crypto import sha256d
       -from .lnmsg import encode_msg
       -
       -if TYPE_CHECKING:
       -    from .network import Network
       -    from .lnrouter import ChannelDB
       -
       -
       -class LNChannelVerifier(NetworkJobOnDefaultServer):
       -    """ Verify channel announcements for the Channel DB """
       -
       -    # FIXME the initial routing sync is bandwidth-heavy, and the electrum server
       -    # will start throttling us, making it even slower. one option would be to
       -    # spread it over multiple servers.
       -
       -    def __init__(self, network: 'Network', channel_db: 'ChannelDB'):
       -        NetworkJobOnDefaultServer.__init__(self, network)
       -        self.channel_db = channel_db
       -        self.lock = threading.Lock()
       -        self.unverified_channel_info = {}  # short_channel_id -> channel_info
       -        # channel announcements that seem to be invalid:
       -        self.blacklist = set()  # short_channel_id
       -
       -    def _reset(self):
       -        super()._reset()
       -        self.started_verifying_channel = set()  # short_channel_id
       -
       -    # TODO make async; and rm self.lock completely
       -    def add_new_channel_info(self, channel_info):
       -        short_channel_id = channel_info.channel_id
       -        if short_channel_id in self.unverified_channel_info:
       -            return
       -        if short_channel_id in self.blacklist:
       -            return
       -        if not verify_sigs_for_channel_announcement(channel_info.msg_payload):
       -            return
       -        with self.lock:
       -            self.unverified_channel_info[short_channel_id] = channel_info
       -
       -    def get_pending_channel_info(self, short_channel_id):
       -        return self.unverified_channel_info.get(short_channel_id, None)
       -
       -    async def _start_tasks(self):
       -        async with self.group as group:
       -            await group.spawn(self.main)
       -
       -    async def main(self):
       -        while True:
       -            await self._verify_some_channels()
       -            await asyncio.sleep(0.1)
       -
       -    async def _verify_some_channels(self):
       -        blockchain = self.network.blockchain()
       -        local_height = blockchain.height()
       -
       -        with self.lock:
       -            unverified_channel_info = list(self.unverified_channel_info)
       -
       -        for short_channel_id in unverified_channel_info:
       -            if short_channel_id in self.started_verifying_channel:
       -                continue
       -            block_height, tx_pos, output_idx = invert_short_channel_id(short_channel_id)
       -            # only resolve short_channel_id if headers are available.
       -            if block_height <= 0 or block_height > local_height:
       -                continue
       -            header = blockchain.read_header(block_height)
       -            if header is None:
       -                if block_height < constants.net.max_checkpoint():
       -                    await self.group.spawn(self.network.request_chunk(block_height, None, can_return_early=True))
       -                continue
       -            self.started_verifying_channel.add(short_channel_id)
       -            await self.group.spawn(self.verify_channel(block_height, tx_pos, short_channel_id))
       -            #self.print_error('requested short_channel_id', bh2u(short_channel_id))
       -
       -    async def verify_channel(self, block_height: int, tx_pos: int, short_channel_id: bytes):
       -        # we are verifying channel announcements as they are from untrusted ln peers.
       -        # we use electrum servers to do this. however we don't trust electrum servers either...
       -        try:
       -            result = await self.network.get_txid_from_txpos(block_height, tx_pos, True)
       -        except aiorpcx.jsonrpc.RPCError:
       -            # the electrum server is complaining about the tx_pos for given block.
       -            # it is not clear what to do now, but let's believe the server.
       -            self._blacklist_short_channel_id(short_channel_id)
       -            return
       -        tx_hash = result['tx_hash']
       -        merkle_branch = result['merkle']
       -        # we need to wait if header sync/reorg is still ongoing, hence lock:
       -        async with self.network.bhi_lock:
       -            header = self.network.blockchain().read_header(block_height)
       -        try:
       -            verify_tx_is_in_block(tx_hash, merkle_branch, tx_pos, header, block_height)
       -        except MerkleVerificationFailure as e:
       -            # the electrum server sent an incorrect proof. blame is on server, not the ln peer
       -            raise GracefulDisconnect(e) from e
       -        try:
       -            raw_tx = await self.network.get_transaction(tx_hash)
       -        except aiorpcx.jsonrpc.RPCError as e:
       -            # the electrum server can't find the tx; but it was the
       -            # one who told us about the txid!! blame is on server
       -            raise GracefulDisconnect(e) from e
       -        tx = Transaction(raw_tx)
       -        try:
       -            tx.deserialize()
       -        except Exception:
       -            # either bug in client, or electrum server is evil.
       -            # if we connect to a diff server at some point, let's try again.
       -            self.print_msg("cannot deserialize transaction, skipping", tx_hash)
       -            return
       -        if tx_hash != tx.txid():
       -            # either bug in client, or electrum server is evil.
       -            # if we connect to a diff server at some point, let's try again.
       -            self.print_error(f"received tx does not match expected txid ({tx_hash} != {tx.txid()})")
       -            return
       -        # check funding output
       -        channel_info = self.unverified_channel_info[short_channel_id]
       -        chan_ann = channel_info.msg_payload
       -        redeem_script = funding_output_script_from_keys(chan_ann['bitcoin_key_1'], chan_ann['bitcoin_key_2'])
       -        expected_address = bitcoin.redeem_script_to_address('p2wsh', redeem_script)
       -        output_idx = invert_short_channel_id(short_channel_id)[2]
       -        try:
       -            actual_output = tx.outputs()[output_idx]
       -        except IndexError:
       -            self._blacklist_short_channel_id(short_channel_id)
       -            return
       -        if expected_address != actual_output.address:
       -            # FIXME what now? best would be to ban the originating ln peer.
       -            self.print_error(f"funding output script mismatch for {bh2u(short_channel_id)}")
       -            self._remove_channel_from_unverified_db(short_channel_id)
       -            return
       -        # put channel into channel DB
       -        channel_info.set_capacity(actual_output.value)
       -        self.channel_db.add_verified_channel_info(short_channel_id, channel_info)
       -        self._remove_channel_from_unverified_db(short_channel_id)
       -
       -    def _remove_channel_from_unverified_db(self, short_channel_id: bytes):
       -        with self.lock:
       -            self.unverified_channel_info.pop(short_channel_id, None)
       -        try: self.started_verifying_channel.remove(short_channel_id)
       -        except KeyError: pass
       -
       -    def _blacklist_short_channel_id(self, short_channel_id: bytes) -> None:
       -        self.blacklist.add(short_channel_id)
       -        with self.lock:
       -            self.unverified_channel_info.pop(short_channel_id, None)
       -
       -
       -def verify_sigs_for_channel_announcement(chan_ann: dict) -> bool:
       -    msg_bytes = encode_msg('channel_announcement', **chan_ann)
       -    pre_hash = msg_bytes[2+256:]
       -    h = sha256d(pre_hash)
       -    pubkeys = [chan_ann['node_id_1'], chan_ann['node_id_2'], chan_ann['bitcoin_key_1'], chan_ann['bitcoin_key_2']]
       -    sigs = [chan_ann['node_signature_1'], chan_ann['node_signature_2'], chan_ann['bitcoin_signature_1'], chan_ann['bitcoin_signature_2']]
       -    for pubkey, sig in zip(pubkeys, sigs):
       -        if not ecc.verify_signature(pubkey, sig, h):
       -            return False
       -    return True
       -
       -
       -def verify_sig_for_channel_update(chan_upd: dict, node_id: bytes) -> bool:
       -    msg_bytes = encode_msg('channel_update', **chan_upd)
       -    pre_hash = msg_bytes[2+64:]
       -    h = sha256d(pre_hash)
       -    sig = chan_upd['signature']
       -    if not ecc.verify_signature(node_id, sig, h):
       -        return False
       -    return True
   DIR diff --git a/electrum/lnpeer.py b/electrum/lnpeer.py
       t@@ -0,0 +1,1105 @@
       +#!/usr/bin/env python3
       +#
       +# Copyright (C) 2018 The Electrum developers
       +# Distributed under the MIT software license, see the accompanying
       +# file LICENCE or http://www.opensource.org/licenses/mit-license.php
       +
       +from collections import OrderedDict, defaultdict
       +import json
       +import asyncio
       +import os
       +import time
       +from functools import partial
       +from typing import List, Tuple, Dict, TYPE_CHECKING, Optional, Callable
       +import traceback
       +import sys
       +
       +import aiorpcx
       +
       +from .simple_config import get_config
       +from .crypto import sha256, sha256d
       +from . import bitcoin
       +from . import ecc
       +from .ecc import sig_string_from_r_and_s, get_r_and_s_from_sig_string, der_sig_from_sig_string
       +from . import constants
       +from .util import PrintError, bh2u, print_error, bfh, log_exceptions, list_enabled_bits, ignore_exceptions
       +from .transaction import Transaction, TxOutput
       +from .lnonion import (new_onion_packet, decode_onion_error, OnionFailureCode, calc_hops_data_for_payment,
       +                      process_onion_packet, OnionPacket, construct_onion_error, OnionRoutingFailureMessage)
       +from .lnchannel import Channel, RevokeAndAck, htlcsum
       +from .lnutil import (Outpoint, LocalConfig, RECEIVED, UpdateAddHtlc,
       +                     RemoteConfig, OnlyPubkeyKeypair, ChannelConstraints, RevocationStore,
       +                     funding_output_script, get_per_commitment_secret_from_seed,
       +                     secret_to_pubkey, PaymentFailure, LnLocalFeatures,
       +                     LOCAL, REMOTE, HTLCOwner, generate_keypair, LnKeyFamily,
       +                     get_ln_flag_pair_of_bit, privkey_to_pubkey, UnknownPaymentHash, MIN_FINAL_CLTV_EXPIRY_ACCEPTED,
       +                     LightningPeerConnectionClosed, HandshakeFailed, NotFoundChanAnnouncementForUpdate,
       +                     MINIMUM_MAX_HTLC_VALUE_IN_FLIGHT_ACCEPTED, MAXIMUM_HTLC_MINIMUM_MSAT_ACCEPTED,
       +                     MAXIMUM_REMOTE_TO_SELF_DELAY_ACCEPTED)
       +from .lntransport import LNTransport, LNTransportBase
       +from .lnmsg import encode_msg, decode_msg
       +
       +if TYPE_CHECKING:
       +    from .lnworker import LNWorker
       +    from .lnrouter import RouteEdge
       +
       +
       +def channel_id_from_funding_tx(funding_txid: str, funding_index: int) -> Tuple[bytes, bytes]:
       +    funding_txid_bytes = bytes.fromhex(funding_txid)[::-1]
       +    i = int.from_bytes(funding_txid_bytes, 'big') ^ funding_index
       +    return i.to_bytes(32, 'big'), funding_txid_bytes
       +
       +class Peer(PrintError):
       +
       +    def __init__(self, lnworker: 'LNWorker', pubkey:bytes, transport: LNTransportBase,
       +                 request_initial_sync=False):
       +        self.initialized = asyncio.Event()
       +        self.transport = transport
       +        self.pubkey = pubkey
       +        self.lnworker = lnworker
       +        self.privkey = lnworker.node_keypair.privkey
       +        self.node_ids = [self.pubkey, privkey_to_pubkey(self.privkey)]
       +        self.network = lnworker.network
       +        self.lnwatcher = lnworker.network.lnwatcher
       +        self.channel_db = lnworker.network.channel_db
       +        self.ping_time = 0
       +        self.shutdown_received = defaultdict(asyncio.Future)
       +        self.channel_accepted = defaultdict(asyncio.Queue)
       +        self.channel_reestablished = defaultdict(asyncio.Future)
       +        self.funding_signed = defaultdict(asyncio.Queue)
       +        self.funding_created = defaultdict(asyncio.Queue)
       +        self.revoke_and_ack = defaultdict(asyncio.Queue)
       +        self.commitment_signed = defaultdict(asyncio.Queue)
       +        self.announcement_signatures = defaultdict(asyncio.Queue)
       +        self.closing_signed = defaultdict(asyncio.Queue)
       +        self.payment_preimages = defaultdict(asyncio.Queue)
       +        self.localfeatures = LnLocalFeatures(0)
       +        if request_initial_sync:
       +            self.localfeatures |= LnLocalFeatures.INITIAL_ROUTING_SYNC
       +        self.localfeatures |= LnLocalFeatures.OPTION_DATA_LOSS_PROTECT_REQ
       +        self.attempted_route = {}
       +        self.orphan_channel_updates = OrderedDict()
       +
       +    def send_message(self, message_name: str, **kwargs):
       +        assert type(message_name) is str
       +        self.print_error("Sending '%s'"%message_name.upper())
       +        self.transport.send_bytes(encode_msg(message_name, **kwargs))
       +
       +    async def initialize(self):
       +        if isinstance(self.transport, LNTransport):
       +            await self.transport.handshake()
       +            self.channel_db.add_recent_peer(self.transport.peer_addr)
       +        self.send_message("init", gflen=0, lflen=1, localfeatures=self.localfeatures)
       +
       +    @property
       +    def channels(self) -> Dict[bytes, Channel]:
       +        return self.lnworker.channels_for_peer(self.pubkey)
       +
       +    def diagnostic_name(self):
       +        return self.transport.name()
       +
       +    def ping_if_required(self):
       +        if time.time() - self.ping_time > 120:
       +            self.send_message('ping', num_pong_bytes=4, byteslen=4)
       +            self.ping_time = time.time()
       +
       +    def process_message(self, message):
       +        message_type, payload = decode_msg(message)
       +        try:
       +            f = getattr(self, 'on_' + message_type)
       +        except AttributeError:
       +            self.print_error("Received '%s'" % message_type.upper(), payload)
       +            return
       +        # raw message is needed to check signature
       +        if message_type=='node_announcement':
       +            payload['raw'] = message
       +        execution_result = f(payload)
       +        if asyncio.iscoroutinefunction(f):
       +            asyncio.ensure_future(execution_result)
       +
       +    def on_error(self, payload):
       +        # todo: self.channel_reestablished is not a queue
       +        self.print_error("error", payload["data"].decode("ascii"))
       +        chan_id = payload.get("channel_id")
       +        for d in [ self.channel_accepted, self.funding_signed,
       +                   self.funding_created, self.revoke_and_ack, self.commitment_signed,
       +                   self.announcement_signatures, self.closing_signed ]:
       +            if chan_id in d:
       +                d[chan_id].put_nowait({'error':payload['data']})
       +
       +    def on_ping(self, payload):
       +        l = int.from_bytes(payload['num_pong_bytes'], 'big')
       +        self.send_message('pong', byteslen=l)
       +
       +    def on_pong(self, payload):
       +        pass
       +
       +    def on_accept_channel(self, payload):
       +        temp_chan_id = payload["temporary_channel_id"]
       +        if temp_chan_id not in self.channel_accepted: raise Exception("Got unknown accept_channel")
       +        self.channel_accepted[temp_chan_id].put_nowait(payload)
       +
       +    def on_funding_signed(self, payload):
       +        channel_id = payload['channel_id']
       +        if channel_id not in self.funding_signed: raise Exception("Got unknown funding_signed")
       +        self.funding_signed[channel_id].put_nowait(payload)
       +
       +    def on_funding_created(self, payload):
       +        channel_id = payload['temporary_channel_id']
       +        if channel_id not in self.funding_created: raise Exception("Got unknown funding_created")
       +        self.funding_created[channel_id].put_nowait(payload)
       +
       +    def on_node_announcement(self, payload):
       +        self.channel_db.on_node_announcement(payload)
       +        self.network.trigger_callback('ln_status')
       +
       +    def on_init(self, payload):
       +        if self.initialized.is_set():
       +            self.print_error("ALREADY INITIALIZED BUT RECEIVED INIT")
       +            return
       +        # if they required some even flag we don't have, they will close themselves
       +        # but if we require an even flag they don't have, we close
       +        our_flags = set(list_enabled_bits(self.localfeatures))
       +        their_flags = set(list_enabled_bits(int.from_bytes(payload['localfeatures'], byteorder="big")))
       +        for flag in our_flags:
       +            if flag not in their_flags and get_ln_flag_pair_of_bit(flag) not in their_flags:
       +                # they don't have this feature we wanted :(
       +                if flag % 2 == 0:  # even flags are compulsory
       +                    raise LightningPeerConnectionClosed("remote does not have even flag {}"
       +                                                        .format(str(LnLocalFeatures(1 << flag))))
       +                self.localfeatures ^= 1 << flag  # disable flag
       +        first_timestamp = self.lnworker.get_first_timestamp()
       +        self.send_message('gossip_timestamp_filter', chain_hash=constants.net.rev_genesis_bytes(), first_timestamp=first_timestamp, timestamp_range=b"\xff"*4)
       +        self.initialized.set()
       +
       +    def on_channel_update(self, payload):
       +        try:
       +            self.channel_db.on_channel_update(payload)
       +        except NotFoundChanAnnouncementForUpdate:
       +            # If it's for a direct channel with this peer, save it for later, as it might be
       +            # for our own channel (and we might not yet know the short channel id for that)
       +            short_channel_id = payload['short_channel_id']
       +            self.print_error("not found channel announce for channel update in db", bh2u(short_channel_id))
       +            self.orphan_channel_updates[short_channel_id] = payload
       +            while len(self.orphan_channel_updates) > 10:
       +                self.orphan_channel_updates.popitem(last=False)
       +
       +    def on_channel_announcement(self, payload):
       +        self.channel_db.on_channel_announcement(payload)
       +
       +    def on_announcement_signatures(self, payload):
       +        channel_id = payload['channel_id']
       +        chan = self.channels[payload['channel_id']]
       +        if chan.config[LOCAL].was_announced:
       +            h, local_node_sig, local_bitcoin_sig = self.send_announcement_signatures(chan)
       +        else:
       +            self.announcement_signatures[channel_id].put_nowait(payload)
       +
       +    def handle_disconnect(func):
       +        async def wrapper_func(self, *args, **kwargs):
       +            try:
       +                return await func(self, *args, **kwargs)
       +            except LightningPeerConnectionClosed as e:
       +                self.print_error("disconnecting gracefully. {}".format(e))
       +            finally:
       +                self.close_and_cleanup()
       +                self.lnworker.peers.pop(self.pubkey)
       +        return wrapper_func
       +
       +    @ignore_exceptions  # do not kill main_taskgroup
       +    @log_exceptions
       +    @handle_disconnect
       +    async def main_loop(self):
       +        """
       +        This is used in LNWorker and is necessary so that we don't kill the main
       +        task group. It is not merged with _main_loop, so that we can test if the
       +        correct exceptions are getting thrown using _main_loop.
       +        """
       +        await self._main_loop()
       +
       +    async def _main_loop(self):
       +        """This is separate from main_loop for the tests."""
       +        try:
       +            await asyncio.wait_for(self.initialize(), 10)
       +        except (OSError, asyncio.TimeoutError, HandshakeFailed) as e:
       +            self.print_error('initialize failed, disconnecting: {}'.format(repr(e)))
       +            return
       +        # loop
       +        async for msg in self.transport.read_messages():
       +            self.process_message(msg)
       +            self.ping_if_required()
       +
       +    def close_and_cleanup(self):
       +        try:
       +            if self.transport:
       +                self.transport.close()
       +        except:
       +            pass
       +        for chan in self.channels.values():
       +            if chan.get_state() != 'FORCE_CLOSING':
       +                chan.set_state('DISCONNECTED')
       +            self.network.trigger_callback('channel', chan)
       +
       +    def make_local_config(self, funding_sat: int, push_msat: int, initiator: HTLCOwner) -> LocalConfig:
       +        # key derivation
       +        channel_counter = self.lnworker.get_and_inc_counter_for_channel_keys()
       +        keypair_generator = lambda family: generate_keypair(self.lnworker.ln_keystore, family, channel_counter)
       +        if initiator == LOCAL:
       +            initial_msat = funding_sat * 1000 - push_msat
       +        else:
       +            initial_msat = push_msat
       +        local_config=LocalConfig(
       +            payment_basepoint=keypair_generator(LnKeyFamily.PAYMENT_BASE),
       +            multisig_key=keypair_generator(LnKeyFamily.MULTISIG),
       +            htlc_basepoint=keypair_generator(LnKeyFamily.HTLC_BASE),
       +            delayed_basepoint=keypair_generator(LnKeyFamily.DELAY_BASE),
       +            revocation_basepoint=keypair_generator(LnKeyFamily.REVOCATION_BASE),
       +            to_self_delay=9,
       +            dust_limit_sat=546,
       +            max_htlc_value_in_flight_msat=funding_sat * 1000,
       +            max_accepted_htlcs=5,
       +            initial_msat=initial_msat,
       +            ctn=-1,
       +            next_htlc_id=0,
       +            reserve_sat=546,
       +            per_commitment_secret_seed=keypair_generator(LnKeyFamily.REVOCATION_ROOT).privkey,
       +            funding_locked_received=False,
       +            was_announced=False,
       +            current_commitment_signature=None,
       +            current_htlc_signatures=[],
       +            got_sig_for_next=False,
       +        )
       +        return local_config
       +
       +    @log_exceptions
       +    async def channel_establishment_flow(self, password: Optional[str], funding_sat: int,
       +                                         push_msat: int, temp_channel_id: bytes) -> Channel:
       +        wallet = self.lnworker.wallet
       +        # dry run creating funding tx to see if we even have enough funds
       +        funding_tx_test = wallet.mktx([TxOutput(bitcoin.TYPE_ADDRESS, wallet.dummy_address(), funding_sat)],
       +                                      password, self.lnworker.config, nonlocal_only=True)
       +        await asyncio.wait_for(self.initialized.wait(), 1)
       +        feerate = self.lnworker.current_feerate_per_kw()
       +        local_config = self.make_local_config(funding_sat, push_msat, LOCAL)
       +        # for the first commitment transaction
       +        per_commitment_secret_first = get_per_commitment_secret_from_seed(local_config.per_commitment_secret_seed,
       +                                                                          RevocationStore.START_INDEX)
       +        per_commitment_point_first = secret_to_pubkey(int.from_bytes(per_commitment_secret_first, 'big'))
       +        self.send_message(
       +            "open_channel",
       +            temporary_channel_id=temp_channel_id,
       +            chain_hash=constants.net.rev_genesis_bytes(),
       +            funding_satoshis=funding_sat,
       +            push_msat=push_msat,
       +            dust_limit_satoshis=local_config.dust_limit_sat,
       +            feerate_per_kw=feerate,
       +            max_accepted_htlcs=local_config.max_accepted_htlcs,
       +            funding_pubkey=local_config.multisig_key.pubkey,
       +            revocation_basepoint=local_config.revocation_basepoint.pubkey,
       +            htlc_basepoint=local_config.htlc_basepoint.pubkey,
       +            payment_basepoint=local_config.payment_basepoint.pubkey,
       +            delayed_payment_basepoint=local_config.delayed_basepoint.pubkey,
       +            first_per_commitment_point=per_commitment_point_first,
       +            to_self_delay=local_config.to_self_delay,
       +            max_htlc_value_in_flight_msat=local_config.max_htlc_value_in_flight_msat,
       +            channel_flags=0x00,  # not willing to announce channel
       +            channel_reserve_satoshis=local_config.reserve_sat,
       +            htlc_minimum_msat=1,
       +        )
       +        payload = await asyncio.wait_for(self.channel_accepted[temp_channel_id].get(), 1)
       +        if payload.get('error'):
       +            raise Exception('Remote Lightning peer reported error: ' + repr(payload.get('error')))
       +        remote_per_commitment_point = payload['first_per_commitment_point']
       +        funding_txn_minimum_depth = int.from_bytes(payload['minimum_depth'], 'big')
       +        assert funding_txn_minimum_depth > 0, funding_txn_minimum_depth
       +        remote_dust_limit_sat = int.from_bytes(payload['dust_limit_satoshis'], byteorder='big')
       +        remote_reserve_sat = self.validate_remote_reserve(payload["channel_reserve_satoshis"], remote_dust_limit_sat, funding_sat)
       +        if remote_dust_limit_sat > remote_reserve_sat:
       +            raise Exception(f"Remote Lightning peer reports dust_limit_sat > reserve_sat which is a BOLT-02 protocol violation.")
       +        htlc_min = int.from_bytes(payload['htlc_minimum_msat'], 'big')
       +        if htlc_min > MAXIMUM_HTLC_MINIMUM_MSAT_ACCEPTED:
       +            raise Exception(f"Remote Lightning peer reports htlc_minimum_msat={htlc_min} mSAT," +
       +                    f" which is above Electrums required maximum limit of that parameter ({MAXIMUM_HTLC_MINIMUM_MSAT_ACCEPTED} mSAT).")
       +        remote_max = int.from_bytes(payload['max_htlc_value_in_flight_msat'], 'big')
       +        if remote_max < MINIMUM_MAX_HTLC_VALUE_IN_FLIGHT_ACCEPTED:
       +            raise Exception(f"Remote Lightning peer reports max_htlc_value_in_flight_msat at only {remote_max} mSAT" +
       +                    f" which is below Electrums required minimum ({MINIMUM_MAX_HTLC_VALUE_IN_FLIGHT_ACCEPTED} mSAT).")
       +        max_accepted_htlcs = int.from_bytes(payload["max_accepted_htlcs"], 'big')
       +        if max_accepted_htlcs > 483:
       +            raise Exception("Remote Lightning peer reports max_accepted_htlcs > 483, which is a BOLT-02 protocol violation.")
       +        remote_to_self_delay = int.from_bytes(payload['to_self_delay'], byteorder='big')
       +        if remote_to_self_delay > MAXIMUM_REMOTE_TO_SELF_DELAY_ACCEPTED:
       +            raise Exception(f"Remote Lightning peer reports to_self_delay={remote_to_self_delay}," +
       +                    f" which is above Electrums required maximum ({MAXIMUM_REMOTE_TO_SELF_DELAY_ACCEPTED})")
       +        their_revocation_store = RevocationStore()
       +        remote_config = RemoteConfig(
       +            payment_basepoint=OnlyPubkeyKeypair(payload['payment_basepoint']),
       +            multisig_key=OnlyPubkeyKeypair(payload["funding_pubkey"]),
       +            htlc_basepoint=OnlyPubkeyKeypair(payload['htlc_basepoint']),
       +            delayed_basepoint=OnlyPubkeyKeypair(payload['delayed_payment_basepoint']),
       +            revocation_basepoint=OnlyPubkeyKeypair(payload['revocation_basepoint']),
       +            to_self_delay=remote_to_self_delay,
       +            dust_limit_sat=remote_dust_limit_sat,
       +            max_htlc_value_in_flight_msat=remote_max,
       +            max_accepted_htlcs=max_accepted_htlcs,
       +            initial_msat=push_msat,
       +            ctn = -1,
       +            next_htlc_id = 0,
       +            reserve_sat = remote_reserve_sat,
       +            htlc_minimum_msat = htlc_min,
       +
       +            next_per_commitment_point=remote_per_commitment_point,
       +            current_per_commitment_point=None,
       +            revocation_store=their_revocation_store,
       +        )
       +        # create funding tx
       +        redeem_script = funding_output_script(local_config, remote_config)
       +        funding_address = bitcoin.redeem_script_to_address('p2wsh', redeem_script)
       +        funding_output = TxOutput(bitcoin.TYPE_ADDRESS, funding_address, funding_sat)
       +        funding_tx = wallet.mktx([funding_output], password, self.lnworker.config, nonlocal_only=True)
       +        funding_txid = funding_tx.txid()
       +        funding_index = funding_tx.outputs().index(funding_output)
       +        # remote commitment transaction
       +        channel_id, funding_txid_bytes = channel_id_from_funding_tx(funding_txid, funding_index)
       +        chan_dict = {
       +                "node_id": self.pubkey,
       +                "channel_id": channel_id,
       +                "short_channel_id": None,
       +                "funding_outpoint": Outpoint(funding_txid, funding_index),
       +                "remote_config": remote_config,
       +                "local_config": local_config,
       +                "constraints": ChannelConstraints(capacity=funding_sat, is_initiator=True, funding_txn_minimum_depth=funding_txn_minimum_depth, feerate=feerate),
       +                "remote_commitment_to_be_revoked": None,
       +        }
       +        chan = Channel(chan_dict,
       +                       sweep_address=self.lnworker.sweep_address,
       +                       payment_completed=self.lnworker.payment_completed)
       +        chan.lnwatcher = self.lnwatcher
       +        chan.get_preimage_and_invoice = self.lnworker.get_invoice  # FIXME hack.
       +        sig_64, _ = chan.sign_next_commitment()
       +        self.send_message("funding_created",
       +            temporary_channel_id=temp_channel_id,
       +            funding_txid=funding_txid_bytes,
       +            funding_output_index=funding_index,
       +            signature=sig_64)
       +        payload = await asyncio.wait_for(self.funding_signed[channel_id].get(), 1)
       +        self.print_error('received funding_signed')
       +        remote_sig = payload['signature']
       +        chan.receive_new_commitment(remote_sig, [])
       +        # broadcast funding tx
       +        await asyncio.wait_for(self.network.broadcast_transaction(funding_tx), 1)
       +        chan.remote_commitment_to_be_revoked = chan.pending_commitment(REMOTE)
       +        chan.config[REMOTE] = chan.config[REMOTE]._replace(ctn=0, current_per_commitment_point=remote_per_commitment_point, next_per_commitment_point=None)
       +        chan.config[LOCAL] = chan.config[LOCAL]._replace(ctn=0, current_commitment_signature=remote_sig, got_sig_for_next=False)
       +        chan.set_state('OPENING')
       +        chan.set_remote_commitment()
       +        chan.set_local_commitment(chan.current_commitment(LOCAL))
       +        return chan
       +
       +    async def on_open_channel(self, payload):
       +        # payload['channel_flags']
       +        if payload['chain_hash'] != constants.net.rev_genesis_bytes():
       +            raise Exception('wrong chain_hash')
       +        funding_sat = int.from_bytes(payload['funding_satoshis'], 'big')
       +        push_msat = int.from_bytes(payload['push_msat'], 'big')
       +        feerate = int.from_bytes(payload['feerate_per_kw'], 'big')
       +
       +        temp_chan_id = payload['temporary_channel_id']
       +        local_config = self.make_local_config(funding_sat, push_msat, REMOTE)
       +        # for the first commitment transaction
       +        per_commitment_secret_first = get_per_commitment_secret_from_seed(local_config.per_commitment_secret_seed,
       +                                                                          RevocationStore.START_INDEX)
       +        per_commitment_point_first = secret_to_pubkey(int.from_bytes(per_commitment_secret_first, 'big'))
       +        min_depth = 3
       +        self.send_message('accept_channel',
       +            temporary_channel_id=temp_chan_id,
       +            dust_limit_satoshis=local_config.dust_limit_sat,
       +            max_htlc_value_in_flight_msat=local_config.max_htlc_value_in_flight_msat,
       +            channel_reserve_satoshis=local_config.reserve_sat,
       +            htlc_minimum_msat=1000,
       +            minimum_depth=min_depth,
       +            to_self_delay=local_config.to_self_delay,
       +            max_accepted_htlcs=local_config.max_accepted_htlcs,
       +            funding_pubkey=local_config.multisig_key.pubkey,
       +            revocation_basepoint=local_config.revocation_basepoint.pubkey,
       +            payment_basepoint=local_config.payment_basepoint.pubkey,
       +            delayed_payment_basepoint=local_config.delayed_basepoint.pubkey,
       +            htlc_basepoint=local_config.htlc_basepoint.pubkey,
       +            first_per_commitment_point=per_commitment_point_first,
       +        )
       +        funding_created = await self.funding_created[temp_chan_id].get()
       +        funding_idx = int.from_bytes(funding_created['funding_output_index'], 'big')
       +        funding_txid = bh2u(funding_created['funding_txid'][::-1])
       +        channel_id, funding_txid_bytes = channel_id_from_funding_tx(funding_txid, funding_idx)
       +        their_revocation_store = RevocationStore()
       +        remote_balance_sat = funding_sat * 1000 - push_msat
       +        remote_dust_limit_sat = int.from_bytes(payload['dust_limit_satoshis'], byteorder='big') # TODO validate
       +        remote_reserve_sat = self.validate_remote_reserve(payload['channel_reserve_satoshis'], remote_dust_limit_sat, funding_sat)
       +        chan_dict = {
       +                "node_id": self.pubkey,
       +                "channel_id": channel_id,
       +                "short_channel_id": None,
       +                "funding_outpoint": Outpoint(funding_txid, funding_idx),
       +                "remote_config": RemoteConfig(
       +                    payment_basepoint=OnlyPubkeyKeypair(payload['payment_basepoint']),
       +                    multisig_key=OnlyPubkeyKeypair(payload['funding_pubkey']),
       +                    htlc_basepoint=OnlyPubkeyKeypair(payload['htlc_basepoint']),
       +                    delayed_basepoint=OnlyPubkeyKeypair(payload['delayed_payment_basepoint']),
       +                    revocation_basepoint=OnlyPubkeyKeypair(payload['revocation_basepoint']),
       +                    to_self_delay=int.from_bytes(payload['to_self_delay'], 'big'),
       +                    dust_limit_sat=remote_dust_limit_sat,
       +                    max_htlc_value_in_flight_msat=int.from_bytes(payload['max_htlc_value_in_flight_msat'], 'big'), # TODO validate
       +                    max_accepted_htlcs=int.from_bytes(payload['max_accepted_htlcs'], 'big'), # TODO validate
       +                    initial_msat=remote_balance_sat,
       +                    ctn = -1,
       +                    next_htlc_id = 0,
       +                    reserve_sat = remote_reserve_sat,
       +                    htlc_minimum_msat=int.from_bytes(payload['htlc_minimum_msat'], 'big'), # TODO validate
       +
       +                    next_per_commitment_point=payload['first_per_commitment_point'],
       +                    current_per_commitment_point=None,
       +                    revocation_store=their_revocation_store,
       +                ),
       +                "local_config": local_config,
       +                "constraints": ChannelConstraints(capacity=funding_sat, is_initiator=False, funding_txn_minimum_depth=min_depth, feerate=feerate),
       +                "remote_commitment_to_be_revoked": None,
       +        }
       +        chan = Channel(chan_dict,
       +                       sweep_address=self.lnworker.sweep_address,
       +                       payment_completed=self.lnworker.payment_completed)
       +        chan.lnwatcher = self.lnwatcher
       +        chan.get_preimage_and_invoice = self.lnworker.get_invoice  # FIXME hack.
       +        remote_sig = funding_created['signature']
       +        chan.receive_new_commitment(remote_sig, [])
       +        sig_64, _ = chan.sign_next_commitment()
       +        self.send_message('funding_signed',
       +            channel_id=channel_id,
       +            signature=sig_64,
       +        )
       +        chan.set_state('OPENING')
       +        chan.remote_commitment_to_be_revoked = chan.pending_commitment(REMOTE)
       +        chan.config[REMOTE] = chan.config[REMOTE]._replace(ctn=0, current_per_commitment_point=payload['first_per_commitment_point'], next_per_commitment_point=None)
       +        chan.config[LOCAL] = chan.config[LOCAL]._replace(ctn=0, current_commitment_signature=remote_sig)
       +        self.lnworker.save_channel(chan)
       +        self.lnwatcher.watch_channel(chan.get_funding_address(), chan.funding_outpoint.to_str())
       +        self.lnworker.on_channels_updated()
       +        while True:
       +            try:
       +                funding_tx = Transaction(await self.network.get_transaction(funding_txid))
       +            except aiorpcx.jsonrpc.RPCError as e:
       +                print("sleeping", str(e))
       +                await asyncio.sleep(1)
       +            else:
       +                break
       +        outp = funding_tx.outputs()[funding_idx]
       +        redeem_script = funding_output_script(chan.config[REMOTE], chan.config[LOCAL])
       +        funding_address = bitcoin.redeem_script_to_address('p2wsh', redeem_script)
       +        if outp != TxOutput(bitcoin.TYPE_ADDRESS, funding_address, funding_sat):
       +            chan.set_state('DISCONNECTED')
       +            raise Exception('funding outpoint mismatch')
       +
       +    def validate_remote_reserve(self, payload_field: bytes, dust_limit: int, funding_sat: int) -> int:
       +        remote_reserve_sat = int.from_bytes(payload_field, 'big')
       +        if remote_reserve_sat < dust_limit:
       +            raise Exception('protocol violation: reserve < dust_limit')
       +        if remote_reserve_sat > funding_sat/100:
       +            raise Exception(f'reserve too high: {remote_reserve_sat}, funding_sat: {funding_sat}')
       +        return remote_reserve_sat
       +
       +    def on_channel_reestablish(self, payload):
       +        chan_id = payload["channel_id"]
       +        self.print_error("Received channel_reestablish", bh2u(chan_id))
       +        chan = self.channels.get(chan_id)
       +        if not chan:
       +            self.print_error("Warning: received unknown channel_reestablish", bh2u(chan_id))
       +            return
       +        self.channel_reestablished[chan_id].set_result(payload)
       +
       +    @log_exceptions
       +    async def reestablish_channel(self, chan: Channel):
       +        await self.initialized.wait()
       +        chan_id = chan.channel_id
       +        if chan.get_state() != 'DISCONNECTED':
       +            self.print_error('reestablish_channel was called but channel {} already in state {}'
       +                             .format(chan_id, chan.get_state()))
       +            return
       +        chan.set_state('REESTABLISHING')
       +        self.network.trigger_callback('channel', chan)
       +        self.send_message("channel_reestablish",
       +            channel_id=chan_id,
       +            next_local_commitment_number=chan.config[LOCAL].ctn+1,
       +            next_remote_revocation_number=chan.config[REMOTE].ctn
       +        )
       +        channel_reestablish_msg = await self.channel_reestablished[chan_id]
       +        chan.set_state('OPENING')
       +
       +        def try_to_get_remote_to_force_close_with_their_latest():
       +            self.print_error("trying to get remote to force close", bh2u(chan_id))
       +            self.send_message("channel_reestablish",
       +                                      channel_id=chan_id,
       +                                      next_local_commitment_number=0,
       +                                      next_remote_revocation_number=0
       +                                      )
       +        # compare remote ctns
       +        remote_ctn = int.from_bytes(channel_reestablish_msg["next_local_commitment_number"], 'big')
       +        if remote_ctn != chan.config[REMOTE].ctn + 1:
       +            self.print_error("expected remote ctn {}, got {}".format(chan.config[REMOTE].ctn + 1, remote_ctn))
       +            # TODO iff their ctn is lower than ours, we should force close instead
       +            try_to_get_remote_to_force_close_with_their_latest()
       +            return
       +        # compare local ctns
       +        local_ctn = int.from_bytes(channel_reestablish_msg["next_remote_revocation_number"], 'big')
       +        if local_ctn != chan.config[LOCAL].ctn:
       +            if remote_ctn == chan.config[LOCAL].ctn + 1:
       +                # A node:
       +                #    if next_remote_revocation_number is equal to the
       +                #    commitment number of the last revoke_and_ack
       +                #    the receiving node sent, AND the receiving node
       +                #    hasn't already received a closing_signed:
       +                #        MUST re-send the revoke_and_ack.
       +                chan.config[LOCAL]=chan.config[LOCAL]._replace(
       +                    ctn=remote_ctn,
       +                )
       +                self.send_revoke_and_ack(chan)
       +            else:
       +                self.print_error("expected local ctn {}, got {}".format(chan.config[LOCAL].ctn, local_ctn))
       +                # TODO iff their ctn is lower than ours, we should force close instead
       +                try_to_get_remote_to_force_close_with_their_latest()
       +                return
       +        # compare per commitment points (needs data_protect option)
       +        their_pcp = channel_reestablish_msg.get("my_current_per_commitment_point", None)
       +        if their_pcp is not None:
       +            our_pcp = chan.config[REMOTE].current_per_commitment_point
       +            if our_pcp is None:
       +                our_pcp = chan.config[REMOTE].next_per_commitment_point
       +            if our_pcp != their_pcp:
       +                self.print_error("Remote PCP mismatch: {} {}".format(bh2u(our_pcp), bh2u(their_pcp)))
       +                # FIXME ...what now?
       +                try_to_get_remote_to_force_close_with_their_latest()
       +                return
       +        if remote_ctn == chan.config[LOCAL].ctn+1 == 1 and chan.short_channel_id:
       +            self.send_funding_locked(chan)
       +        # checks done
       +        if chan.config[LOCAL].funding_locked_received and chan.short_channel_id:
       +            self.mark_open(chan)
       +        self.network.trigger_callback('channel', chan)
       +
       +    def send_funding_locked(self, chan: Channel):
       +        channel_id = chan.channel_id
       +        per_commitment_secret_index = RevocationStore.START_INDEX - 1
       +        per_commitment_point_second = secret_to_pubkey(int.from_bytes(
       +            get_per_commitment_secret_from_seed(chan.config[LOCAL].per_commitment_secret_seed, per_commitment_secret_index), 'big'))
       +        # note: if funding_locked was not yet received, we might send it multiple times
       +        self.send_message("funding_locked", channel_id=channel_id, next_per_commitment_point=per_commitment_point_second)
       +        if chan.config[LOCAL].funding_locked_received and chan.short_channel_id:
       +            self.mark_open(chan)
       +
       +    def on_funding_locked(self, payload):
       +        channel_id = payload['channel_id']
       +        chan = self.channels.get(channel_id)
       +        if not chan:
       +            print(self.channels)
       +            raise Exception("Got unknown funding_locked", channel_id)
       +        if not chan.config[LOCAL].funding_locked_received:
       +            our_next_point = chan.config[REMOTE].next_per_commitment_point
       +            their_next_point = payload["next_per_commitment_point"]
       +            new_remote_state = chan.config[REMOTE]._replace(next_per_commitment_point=their_next_point)
       +            new_local_state = chan.config[LOCAL]._replace(funding_locked_received = True)
       +            chan.config[REMOTE]=new_remote_state
       +            chan.config[LOCAL]=new_local_state
       +            self.lnworker.save_channel(chan)
       +        if chan.short_channel_id:
       +            self.mark_open(chan)
       +
       +    def on_network_update(self, chan: Channel, funding_tx_depth: int):
       +        """
       +        Only called when the channel is OPEN.
       +
       +        Runs on the Network thread.
       +        """
       +        if not chan.config[LOCAL].was_announced and funding_tx_depth >= 6:
       +            # don't announce our channels
       +            # FIXME should this be a field in chan.local_state maybe?
       +            return
       +            chan.config[LOCAL]=chan.config[LOCAL]._replace(was_announced=True)
       +            coro = self.handle_announcements(chan)
       +            self.lnworker.save_channel(chan)
       +            asyncio.run_coroutine_threadsafe(coro, self.network.asyncio_loop)
       +
       +    @log_exceptions
       +    async def handle_announcements(self, chan):
       +        h, local_node_sig, local_bitcoin_sig = self.send_announcement_signatures(chan)
       +        announcement_signatures_msg = await self.announcement_signatures[chan.channel_id].get()
       +        remote_node_sig = announcement_signatures_msg["node_signature"]
       +        remote_bitcoin_sig = announcement_signatures_msg["bitcoin_signature"]
       +        if not ecc.verify_signature(chan.config[REMOTE].multisig_key.pubkey, remote_bitcoin_sig, h):
       +            raise Exception("bitcoin_sig invalid in announcement_signatures")
       +        if not ecc.verify_signature(self.pubkey, remote_node_sig, h):
       +            raise Exception("node_sig invalid in announcement_signatures")
       +
       +        node_sigs = [remote_node_sig, local_node_sig]
       +        bitcoin_sigs = [remote_bitcoin_sig, local_bitcoin_sig]
       +        bitcoin_keys = [chan.config[REMOTE].multisig_key.pubkey, chan.config[LOCAL].multisig_key.pubkey]
       +
       +        if self.node_ids[0] > self.node_ids[1]:
       +            node_sigs.reverse()
       +            bitcoin_sigs.reverse()
       +            node_ids = list(reversed(self.node_ids))
       +            bitcoin_keys.reverse()
       +        else:
       +            node_ids = self.node_ids
       +
       +        self.send_message("channel_announcement",
       +            node_signatures_1=node_sigs[0],
       +            node_signatures_2=node_sigs[1],
       +            bitcoin_signature_1=bitcoin_sigs[0],
       +            bitcoin_signature_2=bitcoin_sigs[1],
       +            len=0,
       +            #features not set (defaults to zeros)
       +            chain_hash=constants.net.rev_genesis_bytes(),
       +            short_channel_id=chan.short_channel_id,
       +            node_id_1=node_ids[0],
       +            node_id_2=node_ids[1],
       +            bitcoin_key_1=bitcoin_keys[0],
       +            bitcoin_key_2=bitcoin_keys[1]
       +        )
       +
       +        print("SENT CHANNEL ANNOUNCEMENT")
       +
       +    def mark_open(self, chan: Channel):
       +        assert chan.short_channel_id is not None
       +        if chan.get_state() == "OPEN":
       +            return
       +        # NOTE: even closed channels will be temporarily marked "OPEN"
       +        assert chan.config[LOCAL].funding_locked_received
       +        chan.set_state("OPEN")
       +        self.network.trigger_callback('channel', chan)
       +        # add channel to database
       +        bitcoin_keys = [chan.config[LOCAL].multisig_key.pubkey, chan.config[REMOTE].multisig_key.pubkey]
       +        sorted_node_ids = list(sorted(self.node_ids))
       +        if sorted_node_ids != self.node_ids:
       +            node_ids = sorted_node_ids
       +            bitcoin_keys.reverse()
       +        else:
       +            node_ids = self.node_ids
       +        # note: we inject a channel announcement, and a channel update (for outgoing direction)
       +        # This is atm needed for
       +        # - finding routes
       +        # - the ChanAnn is needed so that we can anchor to it a future ChanUpd
       +        #   that the remote sends, even if the channel was not announced
       +        #   (from BOLT-07: "MAY create a channel_update to communicate the channel
       +        #    parameters to the final node, even though the channel has not yet been announced")
       +        self.channel_db.on_channel_announcement({"short_channel_id": chan.short_channel_id, "node_id_1": node_ids[0], "node_id_2": node_ids[1],
       +                                                 'chain_hash': constants.net.rev_genesis_bytes(), 'len': b'\x00\x00', 'features': b'',
       +                                                 'bitcoin_key_1': bitcoin_keys[0], 'bitcoin_key_2': bitcoin_keys[1]},
       +                                                trusted=True)
       +        # only inject outgoing direction:
       +        if node_ids[0] == privkey_to_pubkey(self.privkey):
       +            channel_flags = b'\x00'
       +        else:
       +            channel_flags = b'\x01'
       +        now = int(time.time()).to_bytes(4, byteorder="big")
       +        self.channel_db.on_channel_update({"short_channel_id": chan.short_channel_id, 'channel_flags': channel_flags, 'cltv_expiry_delta': b'\x90',
       +                                           'htlc_minimum_msat': b'\x03\xe8', 'fee_base_msat': b'\x03\xe8', 'fee_proportional_millionths': b'\x01',
       +                                           'chain_hash': constants.net.rev_genesis_bytes(), 'timestamp': now},
       +                                          trusted=True)
       +        # peer may have sent us a channel update for the incoming direction previously
       +        # note: if we were offline when the 3rd conf happened, lnd will never send us this channel_update
       +        # see https://github.com/lightningnetwork/lnd/issues/1347
       +        #self.send_message("query_short_channel_ids", chain_hash=constants.net.rev_genesis_bytes(),
       +        #                          len=9, encoded_short_ids=b'\x00'+chan.short_channel_id)
       +        pending_channel_update = self.orphan_channel_updates.get(chan.short_channel_id)
       +        if pending_channel_update:
       +            self.channel_db.on_channel_update(pending_channel_update)
       +
       +        self.print_error("CHANNEL OPENING COMPLETED")
       +
       +    def send_announcement_signatures(self, chan: Channel):
       +
       +        bitcoin_keys = [chan.config[REMOTE].multisig_key.pubkey,
       +                        chan.config[LOCAL].multisig_key.pubkey]
       +
       +        sorted_node_ids = list(sorted(self.node_ids))
       +        if sorted_node_ids != self.node_ids:
       +            node_ids = sorted_node_ids
       +            bitcoin_keys.reverse()
       +        else:
       +            node_ids = self.node_ids
       +
       +        chan_ann = encode_msg("channel_announcement",
       +            len=0,
       +            #features not set (defaults to zeros)
       +            chain_hash=constants.net.rev_genesis_bytes(),
       +            short_channel_id=chan.short_channel_id,
       +            node_id_1=node_ids[0],
       +            node_id_2=node_ids[1],
       +            bitcoin_key_1=bitcoin_keys[0],
       +            bitcoin_key_2=bitcoin_keys[1]
       +        )
       +        to_hash = chan_ann[256+2:]
       +        h = sha256d(to_hash)
       +        bitcoin_signature = ecc.ECPrivkey(chan.config[LOCAL].multisig_key.privkey).sign(h, sig_string_from_r_and_s, get_r_and_s_from_sig_string)
       +        node_signature = ecc.ECPrivkey(self.privkey).sign(h, sig_string_from_r_and_s, get_r_and_s_from_sig_string)
       +        self.send_message("announcement_signatures",
       +            channel_id=chan.channel_id,
       +            short_channel_id=chan.short_channel_id,
       +            node_signature=node_signature,
       +            bitcoin_signature=bitcoin_signature
       +        )
       +
       +        return h, node_signature, bitcoin_signature
       +
       +    @log_exceptions
       +    async def on_update_fail_htlc(self, payload):
       +        channel_id = payload["channel_id"]
       +        htlc_id = int.from_bytes(payload["id"], "big")
       +        key = (channel_id, htlc_id)
       +        try:
       +            route = self.attempted_route[key]
       +        except KeyError:
       +            # the remote might try to fail an htlc after we restarted...
       +            # attempted_route is not persisted, so we will get here then
       +            self.print_error("UPDATE_FAIL_HTLC. cannot decode! attempted route is MISSING. {}".format(key))
       +        else:
       +            try:
       +                await self._handle_error_code_from_failed_htlc(payload["reason"], route, channel_id, htlc_id)
       +            except Exception:
       +                # exceptions are suppressed as failing to handle an error code
       +                # should not block us from removing the htlc
       +                traceback.print_exc(file=sys.stderr)
       +        # process update_fail_htlc on channel
       +        chan = self.channels[channel_id]
       +        chan.receive_fail_htlc(htlc_id)
       +        await self.receive_and_revoke(chan)
       +        self.network.trigger_callback('ln_message', self.lnworker, 'Payment failed', htlc_id)
       +
       +    async def _handle_error_code_from_failed_htlc(self, error_reason, route: List['RouteEdge'], channel_id, htlc_id):
       +        chan = self.channels[channel_id]
       +        failure_msg, sender_idx = decode_onion_error(error_reason,
       +                                                     [x.node_id for x in route],
       +                                                     chan.onion_keys[htlc_id])
       +        code, data = failure_msg.code, failure_msg.data
       +        self.print_error("UPDATE_FAIL_HTLC", repr(code), data)
       +        self.print_error(f"error reported by {bh2u(route[sender_idx].node_id)}")
       +        # handle some specific error codes
       +        failure_codes = {
       +            OnionFailureCode.TEMPORARY_CHANNEL_FAILURE: 2,
       +            OnionFailureCode.AMOUNT_BELOW_MINIMUM: 10,
       +            OnionFailureCode.FEE_INSUFFICIENT: 10,
       +            OnionFailureCode.INCORRECT_CLTV_EXPIRY: 6,
       +            OnionFailureCode.EXPIRY_TOO_SOON: 2,
       +            OnionFailureCode.CHANNEL_DISABLED: 4,
       +        }
       +        offset = failure_codes.get(code)
       +        if offset:
       +            channel_update = (258).to_bytes(length=2, byteorder="big") + data[offset:]
       +            message_type, payload = decode_msg(channel_update)
       +            try:
       +                self.print_error("trying to apply channel update on our db", payload)
       +                self.channel_db.on_channel_update(payload)
       +                self.print_error("successfully applied channel update on our db")
       +            except NotFoundChanAnnouncementForUpdate:
       +                # maybe it is a private channel (and data in invoice was outdated)
       +                self.print_error("maybe channel update is for private channel?")
       +                start_node_id = route[sender_idx].node_id
       +                self.channel_db.add_channel_update_for_private_channel(payload, start_node_id)
       +        else:
       +            # blacklist channel after reporter node
       +            # TODO this should depend on the error (even more granularity)
       +            # also, we need finer blacklisting (directed edges; nodes)
       +            try:
       +                short_chan_id = route[sender_idx + 1].short_channel_id
       +            except IndexError:
       +                self.print_error("payment destination reported error")
       +            else:
       +                self.network.path_finder.blacklist.add(short_chan_id)
       +
       +    def send_commitment(self, chan: Channel):
       +        sig_64, htlc_sigs = chan.sign_next_commitment()
       +        self.send_message("commitment_signed", channel_id=chan.channel_id, signature=sig_64, num_htlcs=len(htlc_sigs), htlc_signature=b"".join(htlc_sigs))
       +        return len(htlc_sigs)
       +
       +    async def send_and_revoke(self, chan: Channel):
       +        """ generic channel update flow """
       +        self.send_commitment(chan)
       +        await self.receive_revoke_and_ack(chan)
       +        await self.receive_commitment(chan)
       +        self.send_revoke_and_ack(chan)
       +
       +    async def receive_and_revoke(self, chan: Channel):
       +        await self.receive_commitment(chan)
       +        self.send_revoke_and_ack(chan)
       +        self.send_commitment(chan)
       +        await self.receive_revoke_and_ack(chan)
       +
       +    async def pay(self, route: List['RouteEdge'], chan: Channel, amount_msat: int,
       +                  payment_hash: bytes, min_final_cltv_expiry: int):
       +        assert chan.get_state() == "OPEN", chan.get_state()
       +        assert amount_msat > 0, "amount_msat is not greater zero"
       +        # create onion packet
       +        final_cltv = self.network.get_local_height() + min_final_cltv_expiry
       +        hops_data, amount_msat, cltv = calc_hops_data_for_payment(route, amount_msat, final_cltv)
       +        assert final_cltv <= cltv, (final_cltv, cltv)
       +        secret_key = os.urandom(32)
       +        onion = new_onion_packet([x.node_id for x in route], secret_key, hops_data, associated_data=payment_hash)
       +        # create htlc
       +        htlc = {'amount_msat':amount_msat, 'payment_hash':payment_hash, 'cltv_expiry':cltv}
       +        htlc_id = chan.add_htlc(htlc)
       +        chan.onion_keys[htlc_id] = secret_key
       +        self.attempted_route[(chan.channel_id, htlc_id)] = route
       +        self.print_error(f"starting payment. route: {route}")
       +        self.send_message("update_add_htlc",
       +                          channel_id=chan.channel_id,
       +                          id=htlc_id,
       +                          cltv_expiry=cltv,
       +                          amount_msat=amount_msat,
       +                          payment_hash=payment_hash,
       +                          onion_routing_packet=onion.to_bytes())
       +        await self.send_and_revoke(chan)
       +        return UpdateAddHtlc(**htlc, htlc_id=htlc_id)
       +
       +    async def receive_revoke_and_ack(self, chan: Channel):
       +        revoke_and_ack_msg = await self.revoke_and_ack[chan.channel_id].get()
       +        chan.receive_revocation(RevokeAndAck(revoke_and_ack_msg["per_commitment_secret"], revoke_and_ack_msg["next_per_commitment_point"]))
       +        self.lnworker.save_channel(chan)
       +
       +    def send_revoke_and_ack(self, chan: Channel):
       +        rev, _ = chan.revoke_current_commitment()
       +        self.lnworker.save_channel(chan)
       +        self.send_message("revoke_and_ack",
       +            channel_id=chan.channel_id,
       +            per_commitment_secret=rev.per_commitment_secret,
       +            next_per_commitment_point=rev.next_per_commitment_point)
       +
       +    async def receive_commitment(self, chan: Channel, commitment_signed_msg=None):
       +        if commitment_signed_msg is None:
       +            commitment_signed_msg = await self.commitment_signed[chan.channel_id].get()
       +        data = commitment_signed_msg["htlc_signature"]
       +        htlc_sigs = [data[i:i+64] for i in range(0, len(data), 64)]
       +        chan.receive_new_commitment(commitment_signed_msg["signature"], htlc_sigs)
       +        return len(htlc_sigs)
       +
       +    def on_commitment_signed(self, payload):
       +        self.print_error("commitment_signed", payload)
       +        channel_id = payload['channel_id']
       +        self.commitment_signed[channel_id].put_nowait(payload)
       +
       +    @log_exceptions
       +    async def on_update_fulfill_htlc(self, update_fulfill_htlc_msg):
       +        self.print_error("update_fulfill")
       +        chan = self.channels[update_fulfill_htlc_msg["channel_id"]]
       +        preimage = update_fulfill_htlc_msg["payment_preimage"]
       +        htlc_id = int.from_bytes(update_fulfill_htlc_msg["id"], "big")
       +        chan.receive_htlc_settle(preimage, htlc_id)
       +        await self.receive_and_revoke(chan)
       +        self.network.trigger_callback('ln_message', self.lnworker, 'Payment sent', htlc_id)
       +        # used in lightning-integration
       +        self.payment_preimages[sha256(preimage)].put_nowait(preimage)
       +
       +    def on_update_fail_malformed_htlc(self, payload):
       +        self.print_error("error", payload["data"].decode("ascii"))
       +
       +    @log_exceptions
       +    async def on_update_add_htlc(self, payload):
       +        # no onion routing for the moment: we assume we are the end node
       +        self.print_error('on_update_add_htlc')
       +        # check if this in our list of requests
       +        payment_hash = payload["payment_hash"]
       +        channel_id = payload['channel_id']
       +        htlc_id = int.from_bytes(payload["id"], 'big')
       +        cltv_expiry = int.from_bytes(payload["cltv_expiry"], 'big')
       +        amount_msat_htlc = int.from_bytes(payload["amount_msat"], 'big')
       +        onion_packet = OnionPacket.from_bytes(payload["onion_routing_packet"])
       +        processed_onion = process_onion_packet(onion_packet, associated_data=payment_hash, our_onion_private_key=self.privkey)
       +        chan = self.channels[channel_id]
       +        assert chan.get_state() == "OPEN"
       +        assert htlc_id == chan.config[REMOTE].next_htlc_id, (htlc_id, chan.config[REMOTE].next_htlc_id)  # TODO fail channel instead
       +        if cltv_expiry >= 500_000_000:
       +            pass  # TODO fail the channel
       +        # add htlc
       +        htlc = {'amount_msat': amount_msat_htlc, 'payment_hash':payment_hash, 'cltv_expiry':cltv_expiry}
       +        htlc_id = chan.receive_htlc(htlc)
       +        await self.receive_and_revoke(chan)
       +        # Forward HTLC
       +        # FIXME: this is not robust to us going offline before payment is fulfilled
       +        if not processed_onion.are_we_final:
       +            dph = processed_onion.hop_data.per_hop
       +            next_chan = self.lnworker.get_channel_by_short_id(dph.short_channel_id)
       +            next_peer = self.lnworker.peers[next_chan.node_id]
       +            if next_chan is None or next_chan.get_state() != 'OPEN':
       +                self.print_error("cannot forward htlc", next_chan.get_state() if next_chan else None)
       +                reason = OnionRoutingFailureMessage(code=OnionFailureCode.PERMANENT_CHANNEL_FAILURE, data=b'')
       +                await self.fail_htlc(chan, htlc_id, onion_packet, reason)
       +                return
       +            self.print_error('forwarding htlc to', next_chan.node_id)
       +            next_cltv_expiry = int.from_bytes(dph.outgoing_cltv_value, 'big')
       +            next_amount_msat_htlc = int.from_bytes(dph.amt_to_forward, 'big')
       +            next_htlc = {'amount_msat':next_amount_msat_htlc, 'payment_hash':payment_hash, 'cltv_expiry':next_cltv_expiry}
       +            next_htlc_id = next_chan.add_htlc(next_htlc)
       +            next_peer.send_message(
       +                "update_add_htlc",
       +                channel_id=next_chan.channel_id,
       +                id=next_htlc_id,
       +                cltv_expiry=dph.outgoing_cltv_value,
       +                amount_msat=dph.amt_to_forward,
       +                payment_hash=payment_hash,
       +                onion_routing_packet=processed_onion.next_packet.to_bytes()
       +            )
       +            await next_peer.send_and_revoke(next_chan)
       +            # wait until we get paid
       +            preimage = await next_peer.payment_preimages[payment_hash].get()
       +            # fulfill the original htlc
       +            await self.fulfill_htlc(chan, htlc_id, preimage)
       +            self.print_error("htlc forwarded successfully")
       +            return
       +        try:
       +            preimage, invoice = self.lnworker.get_invoice(payment_hash)
       +        except UnknownPaymentHash:
       +            reason = OnionRoutingFailureMessage(code=OnionFailureCode.UNKNOWN_PAYMENT_HASH, data=b'')
       +            await self.fail_htlc(chan, htlc_id, onion_packet, reason)
       +            return
       +        expected_received_msat = int(invoice.amount * bitcoin.COIN * 1000) if invoice.amount is not None else None
       +        if expected_received_msat is not None and \
       +                (amount_msat_htlc < expected_received_msat or amount_msat_htlc > 2 * expected_received_msat):
       +            reason = OnionRoutingFailureMessage(code=OnionFailureCode.INCORRECT_PAYMENT_AMOUNT, data=b'')
       +            await self.fail_htlc(chan, htlc_id, onion_packet, reason)
       +            return
       +        local_height = self.network.get_local_height()
       +        if local_height + MIN_FINAL_CLTV_EXPIRY_ACCEPTED > cltv_expiry:
       +            reason = OnionRoutingFailureMessage(code=OnionFailureCode.FINAL_EXPIRY_TOO_SOON, data=b'')
       +            await self.fail_htlc(chan, htlc_id, onion_packet, reason)
       +            return
       +        cltv_from_onion = int.from_bytes(processed_onion.hop_data.per_hop.outgoing_cltv_value, byteorder="big")
       +        if cltv_from_onion != cltv_expiry:
       +            reason = OnionRoutingFailureMessage(code=OnionFailureCode.FINAL_INCORRECT_CLTV_EXPIRY,
       +                                                data=cltv_expiry.to_bytes(4, byteorder="big"))
       +            await self.fail_htlc(chan, htlc_id, onion_packet, reason)
       +            return
       +        amount_from_onion = int.from_bytes(processed_onion.hop_data.per_hop.amt_to_forward, byteorder="big")
       +        if amount_from_onion > amount_msat_htlc:
       +            reason = OnionRoutingFailureMessage(code=OnionFailureCode.FINAL_INCORRECT_HTLC_AMOUNT,
       +                                                data=amount_msat_htlc.to_bytes(8, byteorder="big"))
       +            await self.fail_htlc(chan, htlc_id, onion_packet, reason)
       +            return
       +        self.network.trigger_callback('htlc_added', UpdateAddHtlc(**htlc, htlc_id=htlc_id), invoice, RECEIVED)
       +        # settle htlc
       +        if not self.network.config.debug_lightning_do_not_settle:
       +            # settle htlc
       +            await self.fulfill_htlc(chan, htlc_id, preimage)
       +
       +    async def fulfill_htlc(self, chan: Channel, htlc_id: int, preimage: bytes):
       +        chan.settle_htlc(preimage, htlc_id)
       +        self.send_message("update_fulfill_htlc",
       +                          channel_id=chan.channel_id,
       +                          id=htlc_id,
       +                          payment_preimage=preimage)
       +        await self.send_and_revoke(chan)
       +        self.network.trigger_callback('ln_message', self.lnworker, 'Payment received', htlc_id)
       +
       +    async def fail_htlc(self, chan: Channel, htlc_id: int, onion_packet: OnionPacket,
       +                        reason: OnionRoutingFailureMessage):
       +        self.print_error(f"failing received htlc {(bh2u(chan.channel_id), htlc_id)}. reason: {reason}")
       +        chan.fail_htlc(htlc_id)
       +        error_packet = construct_onion_error(reason, onion_packet, our_onion_private_key=self.privkey)
       +        self.send_message("update_fail_htlc",
       +                          channel_id=chan.channel_id,
       +                          id=htlc_id,
       +                          len=len(error_packet),
       +                          reason=error_packet)
       +        await self.send_and_revoke(chan)
       +
       +    def on_revoke_and_ack(self, payload):
       +        self.print_error("got revoke_and_ack")
       +        channel_id = payload["channel_id"]
       +        self.revoke_and_ack[channel_id].put_nowait(payload)
       +
       +    def on_update_fee(self, payload):
       +        channel_id = payload["channel_id"]
       +        feerate =int.from_bytes(payload["feerate_per_kw"], "big")
       +        self.channels[channel_id].update_fee(feerate, False)
       +
       +    async def bitcoin_fee_update(self, chan: Channel):
       +        """
       +        called when our fee estimates change
       +        """
       +        if not chan.constraints.is_initiator:
       +            # TODO force close if initiator does not update_fee enough
       +            return
       +        feerate_per_kw = self.lnworker.current_feerate_per_kw()
       +        chan_fee = chan.pending_feerate(REMOTE)
       +        self.print_error("current pending feerate", chan_fee)
       +        self.print_error("new feerate", feerate_per_kw)
       +        if feerate_per_kw < chan_fee / 2:
       +            self.print_error("FEES HAVE FALLEN")
       +        elif feerate_per_kw > chan_fee * 2:
       +            self.print_error("FEES HAVE RISEN")
       +        else:
       +            return
       +        chan.update_fee(feerate_per_kw, True)
       +        self.send_message("update_fee",
       +                          channel_id=chan.channel_id,
       +                          feerate_per_kw=feerate_per_kw)
       +        await self.send_and_revoke(chan)
       +
       +    def on_closing_signed(self, payload):
       +        chan_id = payload["channel_id"]
       +        if chan_id not in self.closing_signed: raise Exception("Got unknown closing_signed")
       +        self.closing_signed[chan_id].put_nowait(payload)
       +
       +    @log_exceptions
       +    async def close_channel(self, chan_id: bytes):
       +        chan = self.channels[chan_id]
       +        self.shutdown_received[chan_id] = asyncio.Future()
       +        self.send_shutdown(chan)
       +        payload = await self.shutdown_received[chan_id]
       +        txid = await self._shutdown(chan, payload, True)
       +        self.print_error('Channel closed', txid)
       +        return txid
       +
       +    @log_exceptions
       +    async def on_shutdown(self, payload):
       +        # length of scripts allowed in BOLT-02
       +        if int.from_bytes(payload['len'], 'big') not in (3+20+2, 2+20+1, 2+20, 2+32):
       +            raise Exception('scriptpubkey length in received shutdown message invalid: ' + str(payload['len']))
       +        chan_id = payload['channel_id']
       +        if chan_id in self.shutdown_received:
       +            self.shutdown_received[chan_id].set_result(payload)
       +        else:
       +            chan = self.channels[chan_id]
       +            self.send_shutdown(chan)
       +            txid = await self._shutdown(chan, payload, False)
       +            self.print_error('Channel closed by remote peer', txid)
       +
       +    def send_shutdown(self, chan: Channel):
       +        scriptpubkey = bfh(bitcoin.address_to_script(chan.sweep_address))
       +        self.send_message('shutdown', channel_id=chan.channel_id, len=len(scriptpubkey), scriptpubkey=scriptpubkey)
       +
       +    @log_exceptions
       +    async def _shutdown(self, chan: Channel, payload, is_local):
       +        # set state so that we stop accepting HTLCs
       +        chan.set_state('CLOSING')
       +        while len(chan.hm.htlcs_by_direction(LOCAL, RECEIVED)) > 0:
       +            self.print_error('waiting for htlcs to settle...')
       +            await asyncio.sleep(1)
       +        our_fee = chan.pending_local_fee()
       +        scriptpubkey = bfh(bitcoin.address_to_script(chan.sweep_address))
       +        # negociate fee
       +        while True:
       +            our_sig, closing_tx = chan.make_closing_tx(scriptpubkey, payload['scriptpubkey'], fee_sat=our_fee)
       +            self.send_message('closing_signed', channel_id=chan.channel_id, fee_satoshis=our_fee, signature=our_sig)
       +            cs_payload = await asyncio.wait_for(self.closing_signed[chan.channel_id].get(), 1)
       +            their_fee = int.from_bytes(cs_payload['fee_satoshis'], 'big')
       +            their_sig = cs_payload['signature']
       +            if our_fee == their_fee:
       +                break
       +            # TODO: negociate better
       +            our_fee = their_fee
       +        # index of our_sig
       +        i = bool(chan.get_local_index())
       +        if not is_local: i = not i
       +        # add signatures
       +        closing_tx.add_signature_to_txin(0, int(i), bh2u(der_sig_from_sig_string(our_sig) + b'\x01'))
       +        closing_tx.add_signature_to_txin(0, int(not i), bh2u(der_sig_from_sig_string(their_sig) + b'\x01'))
       +        # broadcast
       +        await self.network.broadcast_transaction(closing_tx)
       +        return closing_tx.txid()
   DIR diff --git a/electrum/lnrouter.py b/electrum/lnrouter.py
       t@@ -36,14 +36,14 @@ import asyncio
        from . import constants
        from .util import PrintError, bh2u, profiler, get_headers_dir, bfh, is_ip_address, list_enabled_bits
        from .storage import JsonDB
       -from .lnchannelverifier import LNChannelVerifier, verify_sig_for_channel_update
       +from .lnverifier import LNChannelVerifier, verify_sig_for_channel_update
        from .crypto import sha256d
        from . import ecc
        from .lnutil import (LN_GLOBAL_FEATURES_KNOWN_SET, LNPeerAddr, NUM_MAX_EDGES_IN_PAYMENT_PATH,
                             NotFoundChanAnnouncementForUpdate)
        
        if TYPE_CHECKING:
       -    from .lnchan import Channel
       +    from .lnchannel import Channel
            from .network import Network
        
        
   DIR diff --git a/electrum/lnsweep.py b/electrum/lnsweep.py
       t@@ -17,7 +17,7 @@ from .transaction import Transaction, TxOutput, construct_witness
        from .simple_config import SimpleConfig, FEERATE_FALLBACK_STATIC_FEE
        
        if TYPE_CHECKING:
       -    from .lnchan import Channel
       +    from .lnchannel import Channel
        
        
        def maybe_create_sweeptx_for_their_ctx_to_remote(ctx: Transaction, sweep_address: str,
       t@@ -203,7 +203,7 @@ def create_sweeptxs_for_their_latest_ctx(chan: 'Channel', ctx: Transaction,
            Regardless of it is a breach or not, construct sweep tx for 'to_remote'.
            If it is a breach, also construct sweep tx for 'to_local'.
            Sweep txns for HTLCs are only constructed if it is NOT a breach, as
       -    lnchan does not store old HTLCs.
       +    lnchannel does not store old HTLCs.
            """
            this_conf, other_conf = get_ordered_channel_configs(chan=chan, for_us=False)
            ctn = extract_ctn_from_tx_and_chan(ctx, chan)
   DIR diff --git a/electrum/lnutil.py b/electrum/lnutil.py
       t@@ -21,7 +21,7 @@ from .lnaddr import lndecode
        from .keystore import BIP32_KeyStore
        
        if TYPE_CHECKING:
       -    from .lnchan import Channel
       +    from .lnchannel import Channel
        
        
        HTLC_TIMEOUT_WEIGHT = 663
       t@@ -114,7 +114,7 @@ MAXIMUM_HTLC_MINIMUM_MSAT_ACCEPTED = 1000
        MAXIMUM_REMOTE_TO_SELF_DELAY_ACCEPTED = 2016
        
        class RevocationStore:
       -    """ Taken from LND, see license in lnchan.py. """
       +    """ Taken from LND, see license in lnchannel.py. """
        
            START_INDEX = 2 ** 48 - 1
        
   DIR diff --git a/electrum/lnverifier.py b/electrum/lnverifier.py
       t@@ -0,0 +1,205 @@
       +# -*- coding: utf-8 -*-
       +#
       +# Electrum - lightweight Bitcoin client
       +# Copyright (C) 2018 The Electrum developers
       +#
       +# Permission is hereby granted, free of charge, to any person
       +# obtaining a copy of this software and associated documentation files
       +# (the "Software"), to deal in the Software without restriction,
       +# including without limitation the rights to use, copy, modify, merge,
       +# publish, distribute, sublicense, and/or sell copies of the Software,
       +# and to permit persons to whom the Software is furnished to do so,
       +# subject to the following conditions:
       +#
       +# The above copyright notice and this permission notice shall be
       +# included in all copies or substantial portions of the Software.
       +#
       +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
       +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
       +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
       +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
       +# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
       +# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
       +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
       +# SOFTWARE.
       +
       +import asyncio
       +import threading
       +from typing import TYPE_CHECKING
       +
       +import aiorpcx
       +
       +from . import bitcoin
       +from . import ecc
       +from . import constants
       +from .util import bh2u, bfh, NetworkJobOnDefaultServer
       +from .lnutil import invert_short_channel_id, funding_output_script_from_keys
       +from .verifier import verify_tx_is_in_block, MerkleVerificationFailure
       +from .transaction import Transaction
       +from .interface import GracefulDisconnect
       +from .crypto import sha256d
       +from .lnmsg import encode_msg
       +
       +if TYPE_CHECKING:
       +    from .network import Network
       +    from .lnrouter import ChannelDB
       +
       +
       +class LNChannelVerifier(NetworkJobOnDefaultServer):
       +    """ Verify channel announcements for the Channel DB """
       +
       +    # FIXME the initial routing sync is bandwidth-heavy, and the electrum server
       +    # will start throttling us, making it even slower. one option would be to
       +    # spread it over multiple servers.
       +
       +    def __init__(self, network: 'Network', channel_db: 'ChannelDB'):
       +        NetworkJobOnDefaultServer.__init__(self, network)
       +        self.channel_db = channel_db
       +        self.lock = threading.Lock()
       +        self.unverified_channel_info = {}  # short_channel_id -> channel_info
       +        # channel announcements that seem to be invalid:
       +        self.blacklist = set()  # short_channel_id
       +
       +    def _reset(self):
       +        super()._reset()
       +        self.started_verifying_channel = set()  # short_channel_id
       +
       +    # TODO make async; and rm self.lock completely
       +    def add_new_channel_info(self, channel_info):
       +        short_channel_id = channel_info.channel_id
       +        if short_channel_id in self.unverified_channel_info:
       +            return
       +        if short_channel_id in self.blacklist:
       +            return
       +        if not verify_sigs_for_channel_announcement(channel_info.msg_payload):
       +            return
       +        with self.lock:
       +            self.unverified_channel_info[short_channel_id] = channel_info
       +
       +    def get_pending_channel_info(self, short_channel_id):
       +        return self.unverified_channel_info.get(short_channel_id, None)
       +
       +    async def _start_tasks(self):
       +        async with self.group as group:
       +            await group.spawn(self.main)
       +
       +    async def main(self):
       +        while True:
       +            await self._verify_some_channels()
       +            await asyncio.sleep(0.1)
       +
       +    async def _verify_some_channels(self):
       +        blockchain = self.network.blockchain()
       +        local_height = blockchain.height()
       +
       +        with self.lock:
       +            unverified_channel_info = list(self.unverified_channel_info)
       +
       +        for short_channel_id in unverified_channel_info:
       +            if short_channel_id in self.started_verifying_channel:
       +                continue
       +            block_height, tx_pos, output_idx = invert_short_channel_id(short_channel_id)
       +            # only resolve short_channel_id if headers are available.
       +            if block_height <= 0 or block_height > local_height:
       +                continue
       +            header = blockchain.read_header(block_height)
       +            if header is None:
       +                if block_height < constants.net.max_checkpoint():
       +                    await self.group.spawn(self.network.request_chunk(block_height, None, can_return_early=True))
       +                continue
       +            self.started_verifying_channel.add(short_channel_id)
       +            await self.group.spawn(self.verify_channel(block_height, tx_pos, short_channel_id))
       +            #self.print_error('requested short_channel_id', bh2u(short_channel_id))
       +
       +    async def verify_channel(self, block_height: int, tx_pos: int, short_channel_id: bytes):
       +        # we are verifying channel announcements as they are from untrusted ln peers.
       +        # we use electrum servers to do this. however we don't trust electrum servers either...
       +        try:
       +            result = await self.network.get_txid_from_txpos(block_height, tx_pos, True)
       +        except aiorpcx.jsonrpc.RPCError:
       +            # the electrum server is complaining about the tx_pos for given block.
       +            # it is not clear what to do now, but let's believe the server.
       +            self._blacklist_short_channel_id(short_channel_id)
       +            return
       +        tx_hash = result['tx_hash']
       +        merkle_branch = result['merkle']
       +        # we need to wait if header sync/reorg is still ongoing, hence lock:
       +        async with self.network.bhi_lock:
       +            header = self.network.blockchain().read_header(block_height)
       +        try:
       +            verify_tx_is_in_block(tx_hash, merkle_branch, tx_pos, header, block_height)
       +        except MerkleVerificationFailure as e:
       +            # the electrum server sent an incorrect proof. blame is on server, not the ln peer
       +            raise GracefulDisconnect(e) from e
       +        try:
       +            raw_tx = await self.network.get_transaction(tx_hash)
       +        except aiorpcx.jsonrpc.RPCError as e:
       +            # the electrum server can't find the tx; but it was the
       +            # one who told us about the txid!! blame is on server
       +            raise GracefulDisconnect(e) from e
       +        tx = Transaction(raw_tx)
       +        try:
       +            tx.deserialize()
       +        except Exception:
       +            # either bug in client, or electrum server is evil.
       +            # if we connect to a diff server at some point, let's try again.
       +            self.print_msg("cannot deserialize transaction, skipping", tx_hash)
       +            return
       +        if tx_hash != tx.txid():
       +            # either bug in client, or electrum server is evil.
       +            # if we connect to a diff server at some point, let's try again.
       +            self.print_error(f"received tx does not match expected txid ({tx_hash} != {tx.txid()})")
       +            return
       +        # check funding output
       +        channel_info = self.unverified_channel_info[short_channel_id]
       +        chan_ann = channel_info.msg_payload
       +        redeem_script = funding_output_script_from_keys(chan_ann['bitcoin_key_1'], chan_ann['bitcoin_key_2'])
       +        expected_address = bitcoin.redeem_script_to_address('p2wsh', redeem_script)
       +        output_idx = invert_short_channel_id(short_channel_id)[2]
       +        try:
       +            actual_output = tx.outputs()[output_idx]
       +        except IndexError:
       +            self._blacklist_short_channel_id(short_channel_id)
       +            return
       +        if expected_address != actual_output.address:
       +            # FIXME what now? best would be to ban the originating ln peer.
       +            self.print_error(f"funding output script mismatch for {bh2u(short_channel_id)}")
       +            self._remove_channel_from_unverified_db(short_channel_id)
       +            return
       +        # put channel into channel DB
       +        channel_info.set_capacity(actual_output.value)
       +        self.channel_db.add_verified_channel_info(short_channel_id, channel_info)
       +        self._remove_channel_from_unverified_db(short_channel_id)
       +
       +    def _remove_channel_from_unverified_db(self, short_channel_id: bytes):
       +        with self.lock:
       +            self.unverified_channel_info.pop(short_channel_id, None)
       +        try: self.started_verifying_channel.remove(short_channel_id)
       +        except KeyError: pass
       +
       +    def _blacklist_short_channel_id(self, short_channel_id: bytes) -> None:
       +        self.blacklist.add(short_channel_id)
       +        with self.lock:
       +            self.unverified_channel_info.pop(short_channel_id, None)
       +
       +
       +def verify_sigs_for_channel_announcement(chan_ann: dict) -> bool:
       +    msg_bytes = encode_msg('channel_announcement', **chan_ann)
       +    pre_hash = msg_bytes[2+256:]
       +    h = sha256d(pre_hash)
       +    pubkeys = [chan_ann['node_id_1'], chan_ann['node_id_2'], chan_ann['bitcoin_key_1'], chan_ann['bitcoin_key_2']]
       +    sigs = [chan_ann['node_signature_1'], chan_ann['node_signature_2'], chan_ann['bitcoin_signature_1'], chan_ann['bitcoin_signature_2']]
       +    for pubkey, sig in zip(pubkeys, sigs):
       +        if not ecc.verify_signature(pubkey, sig, h):
       +            return False
       +    return True
       +
       +
       +def verify_sig_for_channel_update(chan_upd: dict, node_id: bytes) -> bool:
       +    msg_bytes = encode_msg('channel_update', **chan_upd)
       +    pre_hash = msg_bytes[2+64:]
       +    h = sha256d(pre_hash)
       +    sig = chan_upd['signature']
       +    if not ecc.verify_signature(node_id, sig, h):
       +        return False
       +    return True
   DIR diff --git a/electrum/lnworker.py b/electrum/lnworker.py
       t@@ -28,10 +28,10 @@ from .bip32 import bip32_root
        from .util import bh2u, bfh, PrintError, InvoiceError, resolve_dns_srv, is_ip_address, log_exceptions
        from .util import timestamp_to_datetime
        from .lntransport import LNTransport, LNResponderTransport
       -from .lnbase import Peer
       +from .lnpeer import Peer
        from .lnaddr import lnencode, LnAddr, lndecode
        from .ecc import der_sig_from_sig_string
       -from .lnchan import Channel, ChannelJsonEncoder
       +from .lnchannel import Channel, ChannelJsonEncoder
        from .lnutil import (Outpoint, calc_short_channel_id, LNPeerAddr,
                             get_compressed_pubkey_from_bech32, extract_nodeid,
                             PaymentFailure, split_host_port, ConnStringFormatError,
   DIR diff --git a/electrum/tests/test_lnbase.py b/electrum/tests/test_lnbase.py
       t@@ -1,252 +0,0 @@
       -import unittest
       -import asyncio
       -import tempfile
       -from decimal import Decimal
       -import os
       -from contextlib import contextmanager
       -from collections import defaultdict
       -
       -from electrum.network import Network
       -from electrum.ecc import ECPrivkey
       -from electrum import simple_config, lnutil
       -from electrum.lnaddr import lnencode, LnAddr, lndecode
       -from electrum.bitcoin import COIN, sha256
       -from electrum.util import bh2u
       -
       -from electrum.lnbase import Peer
       -from electrum.lnutil import LNPeerAddr, Keypair, privkey_to_pubkey
       -from electrum.lnutil import LightningPeerConnectionClosed, RemoteMisbehaving
       -from electrum.lnutil import PaymentFailure
       -from electrum.lnrouter import ChannelDB, LNPathFinder
       -from electrum.lnworker import LNWorker
       -from electrum.lnmsg import encode_msg, decode_msg
       -
       -from .test_lnchan import create_test_channels
       -
       -def keypair():
       -    priv = ECPrivkey.generate_random_key().get_secret_bytes()
       -    k1 = Keypair(
       -            pubkey=privkey_to_pubkey(priv),
       -            privkey=priv)
       -    return k1
       -
       -@contextmanager
       -def noop_lock():
       -    yield
       -
       -class MockNetwork:
       -    def __init__(self, tx_queue):
       -        self.callbacks = defaultdict(list)
       -        self.lnwatcher = None
       -        user_config = {}
       -        user_dir = tempfile.mkdtemp(prefix="electrum-lnbase-test-")
       -        self.config = simple_config.SimpleConfig(user_config, read_user_dir_function=lambda: user_dir)
       -        self.asyncio_loop = asyncio.get_event_loop()
       -        self.channel_db = ChannelDB(self)
       -        self.interface = None
       -        self.path_finder = LNPathFinder(self.channel_db)
       -        self.tx_queue = tx_queue
       -
       -    @property
       -    def callback_lock(self):
       -        return noop_lock()
       -
       -    register_callback = Network.register_callback
       -    unregister_callback = Network.unregister_callback
       -    trigger_callback = Network.trigger_callback
       -
       -    def get_local_height(self):
       -        return 0
       -
       -    async def broadcast_transaction(self, tx):
       -        if self.tx_queue:
       -            await self.tx_queue.put(tx)
       -
       -class MockStorage:
       -    def put(self, key, value):
       -        pass
       -
       -    def get(self, key, default=None):
       -        pass
       -
       -    def write(self):
       -        pass
       -
       -class MockWallet:
       -    storage = MockStorage()
       -
       -class MockLNWorker:
       -    def __init__(self, remote_keypair, local_keypair, chan, tx_queue):
       -        self.chan = chan
       -        self.remote_keypair = remote_keypair
       -        self.node_keypair = local_keypair
       -        self.network = MockNetwork(tx_queue)
       -        self.channels = {self.chan.channel_id: self.chan}
       -        self.invoices = {}
       -        self.inflight = {}
       -        self.wallet = MockWallet()
       -
       -    @property
       -    def lock(self):
       -        return noop_lock()
       -
       -    @property
       -    def peers(self):
       -        return {self.remote_keypair.pubkey: self.peer}
       -
       -    def channels_for_peer(self, pubkey):
       -        return self.channels
       -
       -    def get_channel_by_short_id(self, short_channel_id):
       -        with self.lock:
       -            for chan in self.channels.values():
       -                if chan.short_channel_id == short_channel_id:
       -                    return chan
       -
       -    def save_channel(self, chan):
       -        print("Ignoring channel save")
       -
       -    def on_channels_updated(self):
       -        pass
       -
       -    def save_invoice(*args):
       -        pass
       -
       -    get_invoice = LNWorker.get_invoice
       -    _create_route_from_invoice = LNWorker._create_route_from_invoice
       -    _check_invoice = staticmethod(LNWorker._check_invoice)
       -    _pay_to_route = LNWorker._pay_to_route
       -    force_close_channel = LNWorker.force_close_channel
       -    get_first_timestamp = lambda self: 0
       -
       -class MockTransport:
       -    def __init__(self):
       -        self.queue = asyncio.Queue()
       -
       -    def name(self):
       -        return ""
       -
       -    async def read_messages(self):
       -        while True:
       -            yield await self.queue.get()
       -
       -class NoFeaturesTransport(MockTransport):
       -    """
       -    This answers the init message with a init that doesn't signal any features.
       -    Used for testing that we require DATA_LOSS_PROTECT.
       -    """
       -    def send_bytes(self, data):
       -        decoded = decode_msg(data)
       -        print(decoded)
       -        if decoded[0] == 'init':
       -            self.queue.put_nowait(encode_msg('init', lflen=1, gflen=1, localfeatures=b"\x00", globalfeatures=b"\x00"))
       -
       -class PutIntoOthersQueueTransport(MockTransport):
       -    def __init__(self):
       -        super().__init__()
       -        self.other_mock_transport = None
       -
       -    def send_bytes(self, data):
       -        self.other_mock_transport.queue.put_nowait(data)
       -
       -def transport_pair():
       -    t1 = PutIntoOthersQueueTransport()
       -    t2 = PutIntoOthersQueueTransport()
       -    t1.other_mock_transport = t2
       -    t2.other_mock_transport = t1
       -    return t1, t2
       -
       -class TestPeer(unittest.TestCase):
       -    def setUp(self):
       -        self.alice_channel, self.bob_channel = create_test_channels()
       -
       -    def test_require_data_loss_protect(self):
       -        mock_lnworker = MockLNWorker(keypair(), keypair(), self.alice_channel, tx_queue=None)
       -        mock_transport = NoFeaturesTransport()
       -        p1 = Peer(mock_lnworker, b"\x00" * 33, mock_transport, request_initial_sync=False)
       -        mock_lnworker.peer = p1
       -        with self.assertRaises(LightningPeerConnectionClosed):
       -            run(asyncio.wait_for(p1._main_loop(), 1))
       -
       -    def prepare_peers(self):
       -        k1, k2 = keypair(), keypair()
       -        t1, t2 = transport_pair()
       -        q1, q2 = asyncio.Queue(), asyncio.Queue()
       -        w1 = MockLNWorker(k1, k2, self.alice_channel, tx_queue=q1)
       -        w2 = MockLNWorker(k2, k1, self.bob_channel, tx_queue=q2)
       -        p1 = Peer(w1, k1.pubkey, t1, request_initial_sync=False)
       -        p2 = Peer(w2, k2.pubkey, t2, request_initial_sync=False)
       -        w1.peer = p1
       -        w2.peer = p2
       -        # mark_open won't work if state is already OPEN.
       -        # so set it to OPENING
       -        self.alice_channel.set_state("OPENING")
       -        self.bob_channel.set_state("OPENING")
       -        # this populates the channel graph:
       -        p1.mark_open(self.alice_channel)
       -        p2.mark_open(self.bob_channel)
       -        return p1, p2, w1, w2, q1, q2
       -
       -    @staticmethod
       -    def prepare_invoice(w2 # receiver
       -            ):
       -        amount_btc = 100000/Decimal(COIN)
       -        payment_preimage = os.urandom(32)
       -        RHASH = sha256(payment_preimage)
       -        addr = LnAddr(
       -                    RHASH,
       -                    amount_btc,
       -                    tags=[('c', lnutil.MIN_FINAL_CLTV_EXPIRY_FOR_INVOICE),
       -                          ('d', 'coffee')
       -                         ])
       -        pay_req = lnencode(addr, w2.node_keypair.privkey)
       -        w2.invoices[bh2u(RHASH)] = (bh2u(payment_preimage), pay_req, True, None)
       -        return pay_req
       -
       -    @staticmethod
       -    def prepare_ln_message_future(w2 # receiver
       -            ):
       -        fut = asyncio.Future()
       -        def evt_set(event, _lnworker, msg, _htlc_id):
       -            fut.set_result(msg)
       -        w2.network.register_callback(evt_set, ['ln_message'])
       -        return fut
       -
       -    def test_payment(self):
       -        p1, p2, w1, w2, _q1, _q2 = self.prepare_peers()
       -        pay_req = self.prepare_invoice(w2)
       -        fut = self.prepare_ln_message_future(w2)
       -
       -        async def pay():
       -            addr, peer, coro = LNWorker._pay(w1, pay_req)
       -            await coro
       -            print("HTLC ADDED")
       -            self.assertEqual(await fut, 'Payment received')
       -            gath.cancel()
       -        gath = asyncio.gather(pay(), p1._main_loop(), p2._main_loop())
       -        with self.assertRaises(asyncio.CancelledError):
       -            run(gath)
       -
       -    def test_channel_usage_after_closing(self):
       -        p1, p2, w1, w2, q1, q2 = self.prepare_peers()
       -        pay_req = self.prepare_invoice(w2)
       -
       -        addr = w1._check_invoice(pay_req)
       -        route = w1._create_route_from_invoice(decoded_invoice=addr)
       -
       -        run(w1.force_close_channel(self.alice_channel.channel_id))
       -        # check if a tx (commitment transaction) was broadcasted:
       -        assert q1.qsize() == 1
       -
       -        with self.assertRaises(PaymentFailure) as e:
       -            w1._create_route_from_invoice(decoded_invoice=addr)
       -        self.assertEqual(str(e.exception), 'No path found')
       -
       -        peer = w1.peers[route[0].node_id]
       -        # AssertionError is ok since we shouldn't use old routes, and the
       -        # route finding should fail when channel is closed
       -        with self.assertRaises(AssertionError):
       -            run(asyncio.gather(w1._pay_to_route(route, addr, pay_req), p1._main_loop(), p2._main_loop()))
       -
       -def run(coro):
       -    asyncio.get_event_loop().run_until_complete(coro)
   DIR diff --git a/electrum/tests/test_lnchan.py b/electrum/tests/test_lnchan.py
       t@@ -1,826 +0,0 @@
       -# Copyright (C) 2018 The Electrum developers
       -# Copyright (C) 2015-2018 The Lightning Network Developers
       -#
       -# Permission is hereby granted, free of charge, to any person obtaining a copy
       -# of this software and associated documentation files (the "Software"), to deal
       -# in the Software without restriction, including without limitation the rights
       -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
       -# copies of the Software, and to permit persons to whom the Software is
       -# furnished to do so, subject to the following conditions:
       -#
       -# The above copyright notice and this permission notice shall be included in
       -# all copies or substantial portions of the Software.
       -#
       -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
       -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
       -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
       -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
       -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
       -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
       -# THE SOFTWARE.
       -
       -import unittest
       -import os
       -import binascii
       -from pprint import pformat
       -
       -from electrum import bitcoin
       -from electrum import lnbase
       -from electrum import lnchan
       -from electrum import lnutil
       -from electrum import bip32 as bip32_utils
       -from electrum.lnutil import SENT, LOCAL, REMOTE, RECEIVED
       -from electrum.ecc import sig_string_from_der_sig
       -from electrum.util import set_verbosity
       -
       -one_bitcoin_in_msat = bitcoin.COIN * 1000
       -
       -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=one_bitcoin_in_msat * 5,
       -                max_accepted_htlcs=5,
       -                initial_msat=remote_amount,
       -                ctn = -1,
       -                next_htlc_id = 0,
       -                reserve_sat=0,
       -                htlc_minimum_msat=1,
       -
       -                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=one_bitcoin_in_msat * 5,
       -                max_accepted_htlcs=5,
       -                initial_msat=local_amount,
       -                ctn = 0,
       -                next_htlc_id = 0,
       -                reserve_sat=0,
       -
       -                per_commitment_secret_seed=seed,
       -                funding_locked_received=True,
       -                was_announced=False,
       -                current_commitment_signature=None,
       -                current_htlc_signatures=None,
       -                got_sig_for_next=False,
       -            ),
       -            "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 = bip32_utils.bip32_root(b"9dk", 'standard')
       -    xprv, xpub = bip32_utils.bip32_private_derivation(xprv, "m/", sequence)
       -    xtype, depth, fingerprint, child_number, c, k = bip32_utils.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(b"\x01"*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(lnutil.privkey_to_pubkey(x), x) for x in alice_raw]
       -    bob_privkeys = [lnutil.Keypair(lnutil.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 = b"\x01" * 32
       -    bob_seed = b"\x02" * 32
       -
       -    alice_first = lnutil.secret_to_pubkey(int.from_bytes(lnutil.get_per_commitment_secret_from_seed(alice_seed, lnutil.RevocationStore.START_INDEX), "big"))
       -    bob_first = lnutil.secret_to_pubkey(int.from_bytes(lnutil.get_per_commitment_secret_from_seed(bob_seed, lnutil.RevocationStore.START_INDEX), "big"))
       -
       -    alice, bob = \
       -        lnchan.Channel(
       -            create_channel_state(funding_txid, funding_index, funding_sat, feerate, True, local_amount, remote_amount, alice_privkeys, bob_pubkeys, alice_seed, None, bob_first, b"\x02"*33, l_dust=200, r_dust=1300, l_csv=5, r_csv=4), name="alice"), \
       -        lnchan.Channel(
       -            create_channel_state(funding_txid, funding_index, funding_sat, feerate, False, remote_amount, local_amount, bob_privkeys, alice_pubkeys, bob_seed, None, alice_first, b"\x01"*33, l_dust=1300, r_dust=200, l_csv=4, r_csv=5), name="bob")
       -
       -    alice.set_state('OPEN')
       -    bob.set_state('OPEN')
       -
       -    a_out = alice.current_commitment(LOCAL).outputs()
       -    b_out = bob.pending_commitment(REMOTE).outputs()
       -    assert a_out == b_out, "\n" + pformat((a_out, b_out))
       -
       -    sig_from_bob, a_htlc_sigs = bob.sign_next_commitment()
       -    sig_from_alice, b_htlc_sigs = alice.sign_next_commitment()
       -
       -    assert len(a_htlc_sigs) == 0
       -    assert len(b_htlc_sigs) == 0
       -
       -    alice.config[LOCAL] = alice.config[LOCAL]._replace(current_commitment_signature=sig_from_bob)
       -    bob.config[LOCAL] = bob.config[LOCAL]._replace(current_commitment_signature=sig_from_alice)
       -
       -    alice.set_local_commitment(alice.current_commitment(LOCAL))
       -    bob.set_local_commitment(bob.current_commitment(LOCAL))
       -
       -    alice_second = lnutil.secret_to_pubkey(int.from_bytes(lnutil.get_per_commitment_secret_from_seed(alice_seed, lnutil.RevocationStore.START_INDEX - 1), "big"))
       -    bob_second = lnutil.secret_to_pubkey(int.from_bytes(lnutil.get_per_commitment_secret_from_seed(bob_seed, lnutil.RevocationStore.START_INDEX - 1), "big"))
       -
       -    alice.config[REMOTE] = alice.config[REMOTE]._replace(next_per_commitment_point=bob_second, current_per_commitment_point=bob_first)
       -    bob.config[REMOTE] = bob.config[REMOTE]._replace(next_per_commitment_point=alice_second, current_per_commitment_point=alice_first)
       -
       -    alice.set_remote_commitment()
       -    bob.set_remote_commitment()
       -
       -    alice.remote_commitment_to_be_revoked = alice.remote_commitment
       -    bob.remote_commitment_to_be_revoked = bob.remote_commitment
       -
       -    alice.config[REMOTE] = alice.config[REMOTE]._replace(ctn=0)
       -    bob.config[REMOTE] = bob.config[REMOTE]._replace(ctn=0)
       -
       -    return alice, bob
       -
       -class TestFee(unittest.TestCase):
       -    """
       -    test
       -    https://github.com/lightningnetwork/lightning-rfc/blob/e0c436bd7a3ed6a028e1cb472908224658a14eca/03-transactions.md#requirements-2
       -    """
       -    def test_fee(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):
       -    maxDiff = 999
       -
       -    def assertOutputExistsByValue(self, tx, amt_sat):
       -        for typ, scr, val in tx.outputs():
       -            if val == amt_sat:
       -                break
       -        else:
       -            self.assertFalse()
       -
       -    @staticmethod
       -    def setUpClass():
       -        set_verbosity(True)
       -
       -    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_dict = {
       -            '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_dict)
       -        self.assertNotEqual(self.alice_channel.hm.htlcs_by_direction(REMOTE, RECEIVED, 1), set())
       -
       -        before = self.bob_channel.balance_minus_outgoing_htlcs(REMOTE)
       -        beforeLocal = self.bob_channel.balance_minus_outgoing_htlcs(LOCAL)
       -
       -        self.bobHtlcIndex = self.bob_channel.receive_htlc(self.htlc_dict)
       -
       -        self.assertEqual(1, self.bob_channel.hm.log[LOCAL]['ctn'] + 1)
       -        self.assertNotEqual(self.bob_channel.hm.htlcs_by_direction(LOCAL, RECEIVED, 1), set())
       -        after  = self.bob_channel.balance_minus_outgoing_htlcs(REMOTE)
       -        afterLocal = self.bob_channel.balance_minus_outgoing_htlcs(LOCAL)
       -
       -        self.assertEqual(before - after, self.htlc_dict['amount_msat'])
       -        self.assertEqual(beforeLocal, afterLocal)
       -
       -        self.bob_pending_remote_balance = after
       -
       -        self.htlc = self.bob_channel.hm.log[REMOTE]['adds'][0]
       -
       -    def test_concurrent_reversed_payment(self):
       -        self.htlc_dict['payment_hash'] = bitcoin.sha256(32 * b'\x02')
       -        self.htlc_dict['amount_msat'] += 1000
       -        bob_idx = self.bob_channel.add_htlc(self.htlc_dict)
       -        alice_idx = self.alice_channel.receive_htlc(self.htlc_dict)
       -        self.alice_channel.receive_new_commitment(*self.bob_channel.sign_next_commitment())
       -        self.assertEqual(len(self.alice_channel.pending_commitment(REMOTE).outputs()), 4)
       -
       -    def test_SimpleAddSettleWorkflow(self):
       -        alice_channel, bob_channel = self.alice_channel, self.bob_channel
       -        htlc = self.htlc
       -
       -        alice_out = alice_channel.current_commitment(LOCAL).outputs()
       -        short_idx, = [idx for idx, x in enumerate(alice_out) if len(x.address) == 42]
       -        long_idx,  = [idx for idx, x in enumerate(alice_out) if len(x.address) == 62]
       -        self.assertLess(alice_out[long_idx].value, 5 * 10**8, alice_out)
       -        self.assertEqual(alice_out[short_idx].value, 5 * 10**8, alice_out)
       -
       -        alice_out = alice_channel.current_commitment(REMOTE).outputs()
       -        short_idx, = [idx for idx, x in enumerate(alice_out) if len(x.address) == 42]
       -        long_idx,  = [idx for idx, x in enumerate(alice_out) if len(x.address) == 62]
       -        self.assertLess(alice_out[short_idx].value, 5 * 10**8)
       -        self.assertEqual(alice_out[long_idx].value, 5 * 10**8)
       -
       -        def com():
       -            return alice_channel.local_commitment
       -
       -        self.assertTrue(alice_channel.signature_fits(com()))
       -
       -        self.assertNotEqual(alice_channel.included_htlcs(REMOTE, RECEIVED, 1), [])
       -        self.assertEqual({0: [], 1: [htlc]}, alice_channel.included_htlcs_in_their_latest_ctxs(LOCAL))
       -        self.assertNotEqual(bob_channel.included_htlcs(REMOTE, SENT, 1), [])
       -        self.assertEqual({0: [], 1: [htlc]}, bob_channel.included_htlcs_in_their_latest_ctxs(REMOTE))
       -        self.assertEqual({0: [], 1: []}, alice_channel.included_htlcs_in_their_latest_ctxs(REMOTE))
       -        self.assertEqual({0: [], 1: []}, bob_channel.included_htlcs_in_their_latest_ctxs(LOCAL))
       -
       -        from electrum.lnutil import extract_ctn_from_tx_and_chan
       -        tx0 = str(alice_channel.force_close_tx())
       -        self.assertEqual(alice_channel.config[LOCAL].ctn, 0)
       -        self.assertEqual(extract_ctn_from_tx_and_chan(alice_channel.force_close_tx(), alice_channel), 0)
       -        self.assertTrue(alice_channel.signature_fits(alice_channel.current_commitment(LOCAL)))
       -
       -        # 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")
       -
       -        self.assertTrue(alice_channel.signature_fits(com()))
       -        self.assertEqual(str(alice_channel.current_commitment(LOCAL)), str(com()))
       -
       -        self.assertEqual(next(iter(alice_channel.hm.pending_htlcs(REMOTE)))[0], RECEIVED)
       -        self.assertEqual(alice_channel.hm.pending_htlcs(REMOTE), bob_channel.hm.pending_htlcs(LOCAL))
       -        self.assertEqual(alice_channel.pending_commitment(REMOTE).outputs(), bob_channel.pending_commitment(LOCAL).outputs())
       -
       -        # 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.
       -        self.assertTrue(bob_channel.signature_fits(bob_channel.current_commitment(LOCAL)))
       -        bob_channel.receive_new_commitment(aliceSig, aliceHtlcSigs)
       -        self.assertTrue(bob_channel.signature_fits(bob_channel.pending_commitment(LOCAL)))
       -
       -        self.assertEqual(bob_channel.config[REMOTE].ctn, 0)
       -        self.assertEqual(bob_channel.included_htlcs(REMOTE, SENT, 1), [htlc])
       -
       -        self.assertEqual({0: [], 1: [htlc]}, alice_channel.included_htlcs_in_their_latest_ctxs(LOCAL))
       -        self.assertEqual({0: [], 1: [htlc]}, bob_channel.included_htlcs_in_their_latest_ctxs(REMOTE))
       -        self.assertEqual({0: [], 1: []}, alice_channel.included_htlcs_in_their_latest_ctxs(REMOTE))
       -        self.assertEqual({0: [], 1: []}, bob_channel.included_htlcs_in_their_latest_ctxs(LOCAL))
       -
       -        # 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_channel.serialize()
       -        self.assertTrue(bob_channel.signature_fits(bob_channel.current_commitment(LOCAL)))
       -
       -        # Bob finally sends 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 her signature.
       -        bobSig, bobHtlcSigs = bob_channel.sign_next_commitment()
       -        self.assertTrue(bob_channel.signature_fits(bob_channel.current_commitment(LOCAL)))
       -
       -        self.assertEqual(len(bobHtlcSigs), 1)
       -
       -        self.assertTrue(alice_channel.signature_fits(com()))
       -        self.assertEqual(str(alice_channel.current_commitment(LOCAL)), str(com()))
       -
       -        self.assertEqual(len(alice_channel.pending_commitment(LOCAL).outputs()), 3)
       -
       -        # 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_channel.serialize()
       -        self.assertEqual(alice_channel.remote_commitment.outputs(), alice_channel.current_commitment(REMOTE).outputs())
       -
       -        self.assertTrue(alice_channel.signature_fits(com()))
       -        self.assertTrue(alice_channel.signature_fits(alice_channel.current_commitment(LOCAL)))
       -        alice_channel.serialize()
       -        self.assertEqual(str(alice_channel.current_commitment(LOCAL)), str(com()))
       -
       -        self.assertEqual(len(alice_channel.current_commitment(LOCAL).outputs()), 2)
       -        self.assertEqual(len(alice_channel.current_commitment(REMOTE).outputs()), 3)
       -        self.assertEqual(len(com().outputs()), 2)
       -        self.assertEqual(len(alice_channel.force_close_tx().outputs()), 2)
       -
       -        self.assertEqual(alice_channel.hm.log.keys(), set([LOCAL, REMOTE]))
       -        self.assertEqual(len(alice_channel.hm.log[LOCAL]['adds']), 1)
       -        alice_channel.serialize()
       -
       -        self.assertEqual(alice_channel.pending_commitment(LOCAL).outputs(),
       -                         bob_channel.pending_commitment(REMOTE).outputs())
       -
       -        # 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)
       -        self.assertEqual(alice_channel.remote_commitment.outputs(), alice_channel.current_commitment(REMOTE).outputs())
       -
       -        self.assertEqual(len(alice_channel.current_commitment(REMOTE).outputs()), 3)
       -        self.assertEqual(len(com().outputs()), 3)
       -        self.assertEqual(len(alice_channel.force_close_tx().outputs()), 3)
       -
       -        self.assertEqual(len(alice_channel.hm.log[LOCAL]['adds']), 1)
       -        alice_channel.serialize()
       -
       -        tx1 = str(alice_channel.force_close_tx())
       -        self.assertNotEqual(tx0, tx1)
       -
       -        # Alice then generates a revocation for bob.
       -        self.assertEqual(alice_channel.remote_commitment.outputs(), alice_channel.current_commitment(REMOTE).outputs())
       -        aliceRevocation, _ = alice_channel.revoke_current_commitment()
       -        alice_channel.serialize()
       -        #self.assertEqual(alice_channel.remote_commitment.outputs(), alice_channel.current_commitment(REMOTE).outputs())
       -
       -        tx2 = str(alice_channel.force_close_tx())
       -        # since alice already has the signature for the next one, it doesn't change her force close tx (it was already the newer one)
       -        self.assertEqual(tx1, tx2)
       -
       -        # 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.
       -        self.assertTrue(bob_channel.signature_fits(bob_channel.current_commitment(LOCAL)))
       -        bob_channel.receive_revocation(aliceRevocation)
       -        bob_channel.serialize()
       -
       -        # 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.
       -        alice_ctx = alice_channel.pending_commitment(LOCAL)
       -        bob_ctx = bob_channel.pending_commitment(LOCAL)
       -        self.assertEqual(len(alice_ctx.outputs()), 3, "alice should have three commitment outputs, instead have %s"% len(alice_ctx.outputs()))
       -        self.assertEqual(len(bob_ctx.outputs()), 3, "bob should have three commitment outputs, instead have %s"% len(bob_ctx.outputs()))
       -        self.assertOutputExistsByValue(alice_ctx, htlc.amount_msat // 1000)
       -        self.assertOutputExistsByValue(bob_ctx, 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)
       -
       -        #self.assertEqual(alice_channel.remote_commitment.outputs(), alice_channel.current_commitment(REMOTE).outputs())
       -        alice_channel.receive_htlc_settle(preimage, self.aliceHtlcIndex)
       -
       -        tx3 = str(alice_channel.force_close_tx())
       -        # just settling a htlc does not change her force close tx
       -        self.assertEqual(tx2, tx3)
       -
       -        bobSig2, bobHtlcSigs2 = bob_channel.sign_next_commitment()
       -        self.assertEqual(len(bobHtlcSigs2), 0)
       -
       -        self.assertEqual(alice_channel.hm.htlcs_by_direction(REMOTE, RECEIVED), [htlc])
       -        self.assertEqual(alice_channel.included_htlcs(REMOTE, RECEIVED, alice_channel.config[REMOTE].ctn), [htlc])
       -        self.assertEqual({1: [htlc], 2: []}, alice_channel.included_htlcs_in_their_latest_ctxs(LOCAL))
       -        self.assertEqual({1: [htlc], 2: []}, bob_channel.included_htlcs_in_their_latest_ctxs(REMOTE))
       -        self.assertEqual({1: [], 2: []}, alice_channel.included_htlcs_in_their_latest_ctxs(REMOTE))
       -        self.assertEqual({1: [], 2: []}, bob_channel.included_htlcs_in_their_latest_ctxs(LOCAL))
       -
       -        alice_ctx_bob_version = bob_channel.pending_commitment(REMOTE).outputs()
       -        alice_ctx_alice_version = alice_channel.pending_commitment(LOCAL).outputs()
       -        self.assertEqual(alice_ctx_alice_version, alice_ctx_bob_version)
       -
       -        alice_channel.receive_new_commitment(bobSig2, bobHtlcSigs2)
       -
       -        tx4 = str(alice_channel.force_close_tx())
       -        self.assertNotEqual(tx3, tx4)
       -
       -        self.assertEqual(alice_channel.balance(LOCAL), 500000000000)
       -        self.assertEqual(1, alice_channel.config[LOCAL].ctn)
       -        self.assertEqual(len(alice_channel.included_htlcs(LOCAL, RECEIVED, ctn=2)), 0)
       -        aliceRevocation2, _ = alice_channel.revoke_current_commitment()
       -        alice_channel.serialize()
       -        aliceSig2, aliceHtlcSigs2 = alice_channel.sign_next_commitment()
       -        self.assertEqual(aliceHtlcSigs2, [], "alice should generate no htlc signatures")
       -        self.assertEqual(len(bob_channel.current_commitment(LOCAL).outputs()), 3)
       -        self.assertEqual(len(bob_channel.pending_commitment(LOCAL).outputs()), 2)
       -        received, sent = bob_channel.receive_revocation(aliceRevocation2)
       -        bob_channel.serialize()
       -        self.assertEqual(received, one_bitcoin_in_msat)
       -
       -        bob_channel.receive_new_commitment(aliceSig2, aliceHtlcSigs2)
       -
       -        bobRevocation2, _ = bob_channel.revoke_current_commitment()
       -        bob_channel.serialize()
       -        alice_channel.receive_revocation(bobRevocation2)
       -        alice_channel.serialize()
       -
       -        # 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")
       -
       -        self.assertEqual(self.bob_pending_remote_balance, self.alice_channel.balance(LOCAL))
       -
       -        alice_channel.update_fee(100000, True)
       -        alice_outputs = alice_channel.pending_commitment(REMOTE).outputs()
       -        old_outputs = bob_channel.pending_commitment(LOCAL).outputs()
       -        bob_channel.update_fee(100000, False)
       -        new_outputs = bob_channel.pending_commitment(LOCAL).outputs()
       -        self.assertNotEqual(old_outputs, new_outputs)
       -        self.assertEqual(alice_outputs, new_outputs)
       -
       -        tx5 = str(alice_channel.force_close_tx())
       -        # sending a fee update does not change her force close tx
       -        self.assertEqual(tx4, tx5)
       -
       -        force_state_transition(alice_channel, bob_channel)
       -
       -        tx6 = str(alice_channel.force_close_tx())
       -        self.assertNotEqual(tx5, tx6)
       -
       -        self.htlc_dict['amount_msat'] *= 5
       -        bob_index = bob_channel.add_htlc(self.htlc_dict)
       -        alice_index = alice_channel.receive_htlc(self.htlc_dict)
       -
       -        bob_channel.pending_commitment(REMOTE)
       -        alice_channel.pending_commitment(LOCAL)
       -
       -        alice_channel.pending_commitment(REMOTE)
       -        bob_channel.pending_commitment(LOCAL)
       -
       -        force_state_transition(bob_channel, alice_channel)
       -        alice_channel.settle_htlc(self.paymentPreimage, alice_index)
       -        bob_channel.receive_htlc_settle(self.paymentPreimage, bob_index)
       -        force_state_transition(bob_channel, alice_channel)
       -        self.assertEqual(alice_channel.total_msat(SENT), one_bitcoin_in_msat, "alice satoshis sent incorrect")
       -        self.assertEqual(alice_channel.total_msat(RECEIVED), 5 * one_bitcoin_in_msat, "alice satoshis received incorrect")
       -        self.assertEqual(bob_channel.total_msat(RECEIVED), one_bitcoin_in_msat, "bob satoshis received incorrect")
       -        self.assertEqual(bob_channel.total_msat(SENT), 5 * one_bitcoin_in_msat, "bob satoshis sent incorrect")
       -
       -        alice_channel.serialize()
       -
       -
       -    def alice_to_bob_fee_update(self, fee=111):
       -        aoldctx = self.alice_channel.pending_commitment(REMOTE).outputs()
       -        self.alice_channel.update_fee(fee, True)
       -        anewctx = self.alice_channel.pending_commitment(REMOTE).outputs()
       -        self.assertNotEqual(aoldctx, anewctx)
       -        boldctx = self.bob_channel.pending_commitment(LOCAL).outputs()
       -        self.bob_channel.update_fee(fee, False)
       -        bnewctx = self.bob_channel.pending_commitment(LOCAL).outputs()
       -        self.assertNotEqual(boldctx, bnewctx)
       -        self.assertEqual(anewctx, bnewctx)
       -        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)
       -
       -    def test_AddHTLCNegativeBalance(self):
       -        # the test in lnd doesn't set the fee to zero.
       -        # probably lnd subtracts commitment fee after deciding weather
       -        # an htlc can be added. so we set the fee to zero so that
       -        # the test can work.
       -        self.alice_to_bob_fee_update(0)
       -        force_state_transition(self.alice_channel, self.bob_channel)
       -
       -        self.htlc_dict['payment_hash'] = bitcoin.sha256(32 * b'\x02')
       -        self.alice_channel.add_htlc(self.htlc_dict)
       -        self.htlc_dict['payment_hash'] = bitcoin.sha256(32 * b'\x03')
       -        self.alice_channel.add_htlc(self.htlc_dict)
       -        # now there are three htlcs (one was in setUp)
       -
       -        # Alice now has an available balance of 2 BTC. We'll add a new HTLC of
       -        # value 2 BTC, which should make Alice's balance negative (since she
       -        # has to pay a commitment fee).
       -        new = dict(self.htlc_dict)
       -        new['amount_msat'] *= 2.5
       -        new['payment_hash'] = bitcoin.sha256(32 * b'\x04')
       -        with self.assertRaises(lnutil.PaymentFailure) as cm:
       -            self.alice_channel.add_htlc(new)
       -        self.assertIn('Not enough local balance', cm.exception.args[0])
       -
       -    def test_sign_commitment_is_pure(self):
       -        force_state_transition(self.alice_channel, self.bob_channel)
       -        self.htlc_dict['payment_hash'] = bitcoin.sha256(b'\x02' * 32)
       -        aliceHtlcIndex = self.alice_channel.add_htlc(self.htlc_dict)
       -        before_signing = self.alice_channel.to_save()
       -        self.alice_channel.sign_next_commitment()
       -        after_signing = self.alice_channel.to_save()
       -        try:
       -            self.assertEqual(before_signing, after_signing)
       -        except:
       -            try:
       -                from deepdiff import DeepDiff
       -            except ImportError:
       -                raise
       -            raise Exception(pformat(DeepDiff(before_signing, after_signing)))
       -
       -class TestAvailableToSpend(unittest.TestCase):
       -    def test_DesyncHTLCs(self):
       -        alice_channel, bob_channel = create_test_channels()
       -
       -        paymentPreimage = b"\x01" * 32
       -        paymentHash = bitcoin.sha256(paymentPreimage)
       -        htlc_dict = {
       -            'payment_hash' : paymentHash,
       -            'amount_msat' :  int(4.1 * one_bitcoin_in_msat),
       -            'cltv_expiry' :  5,
       -        }
       -
       -        alice_idx = alice_channel.add_htlc(htlc_dict)
       -        bob_idx = bob_channel.receive_htlc(htlc_dict)
       -        force_state_transition(alice_channel, bob_channel)
       -        bob_channel.fail_htlc(bob_idx)
       -        alice_channel.receive_fail_htlc(alice_idx)
       -        # Alice now has gotten all her original balance (5 BTC) back, however,
       -        # adding a new HTLC at this point SHOULD fail, since if she adds the
       -        # HTLC and signs the next state, Bob cannot assume she received the
       -        # FailHTLC, and must assume she doesn't have the necessary balance
       -        # available.
       -        # We try adding an HTLC of value 1 BTC, which should fail because the
       -        # balance is unavailable.
       -        htlc_dict = {
       -            'payment_hash' : paymentHash,
       -            'amount_msat' :  one_bitcoin_in_msat,
       -            'cltv_expiry' :  5,
       -        }
       -        with self.assertRaises(lnutil.PaymentFailure):
       -            alice_channel.add_htlc(htlc_dict)
       -        # Now do a state transition, which will ACK the FailHTLC, making Alice
       -        # able to add the new HTLC.
       -        force_state_transition(alice_channel, bob_channel)
       -        alice_channel.add_htlc(htlc_dict)
       -
       -class TestChanReserve(unittest.TestCase):
       -    def setUp(self):
       -        alice_channel, bob_channel = create_test_channels()
       -        alice_min_reserve = int(.5 * one_bitcoin_in_msat // 1000)
       -        # We set Bob's channel reserve to a value that is larger than
       -        # his current balance in the channel. This will ensure that
       -        # after a channel is first opened, Bob can still receive HTLCs
       -        # even though his balance is less than his channel reserve.
       -        bob_min_reserve = 6 * one_bitcoin_in_msat // 1000
       -        # bob min reserve was decided by alice, but applies to bob
       -
       -        alice_channel.config[LOCAL] =\
       -            alice_channel.config[LOCAL]._replace(reserve_sat=bob_min_reserve)
       -        alice_channel.config[REMOTE] =\
       -            alice_channel.config[REMOTE]._replace(reserve_sat=alice_min_reserve)
       -
       -        bob_channel.config[LOCAL] =\
       -            bob_channel.config[LOCAL]._replace(reserve_sat=alice_min_reserve)
       -        bob_channel.config[REMOTE] =\
       -            bob_channel.config[REMOTE]._replace(reserve_sat=bob_min_reserve)
       -
       -        self.alice_channel = alice_channel
       -        self.bob_channel = bob_channel
       -
       -    def test_part1(self):
       -        # Add an HTLC that will increase Bob's balance. This should succeed,
       -        # since Alice stays above her channel reserve, and Bob increases his
       -        # balance (while still being below his channel reserve).
       -        #
       -        # Resulting balances:
       -        #        Alice:        4.5
       -        #        Bob:        5.0
       -        paymentPreimage = b"\x01" * 32
       -        paymentHash = bitcoin.sha256(paymentPreimage)
       -        htlc_dict = {
       -            'payment_hash' : paymentHash,
       -            'amount_msat' :  int(.5 * one_bitcoin_in_msat),
       -            'cltv_expiry' :  5,
       -        }
       -        self.alice_channel.add_htlc(htlc_dict)
       -        self.bob_channel.receive_htlc(htlc_dict)
       -        # Force a state transition, making sure this HTLC is considered valid
       -        # even though the channel reserves are not met.
       -        force_state_transition(self.alice_channel, self.bob_channel)
       -
       -        aliceSelfBalance = self.alice_channel.balance(LOCAL)\
       -                - lnchan.htlcsum(self.alice_channel.hm.htlcs_by_direction(LOCAL, SENT))
       -        bobBalance = self.bob_channel.balance(REMOTE)\
       -                - lnchan.htlcsum(self.alice_channel.hm.htlcs_by_direction(REMOTE, SENT))
       -        self.assertEqual(aliceSelfBalance, one_bitcoin_in_msat*4.5)
       -        self.assertEqual(bobBalance, one_bitcoin_in_msat*5)
       -        # Now let Bob try to add an HTLC. This should fail, since it will
       -        # decrease his balance, which is already below the channel reserve.
       -        #
       -        # Resulting balances:
       -        #        Alice:        4.5
       -        #        Bob:        5.0
       -        with self.assertRaises(lnutil.PaymentFailure):
       -            htlc_dict['payment_hash'] = bitcoin.sha256(32 * b'\x02')
       -            self.bob_channel.add_htlc(htlc_dict)
       -        with self.assertRaises(lnutil.RemoteMisbehaving):
       -            self.alice_channel.receive_htlc(htlc_dict)
       -
       -    def part2(self):
       -        paymentPreimage = b"\x01" * 32
       -        paymentHash = bitcoin.sha256(paymentPreimage)
       -        # Now we'll add HTLC of 3.5 BTC to Alice's commitment, this should put
       -        # Alice's balance at 1.5 BTC.
       -        #
       -        # Resulting balances:
       -        #        Alice:        1.5
       -        #        Bob:        9.5
       -        htlc_dict = {
       -            'payment_hash' : paymentHash,
       -            'amount_msat' :  int(3.5 * one_bitcoin_in_msat),
       -            'cltv_expiry' :  5,
       -        }
       -        self.alice_channel.add_htlc(htlc_dict)
       -        self.bob_channel.receive_htlc(htlc_dict)
       -        # Add a second HTLC of 1 BTC. This should fail because it will take
       -        # Alice's balance all the way down to her channel reserve, but since
       -        # she is the initiator the additional transaction fee makes her
       -        # balance dip below.
       -        htlc_dict['amount_msat'] = one_bitcoin_in_msat
       -        with self.assertRaises(lnutil.PaymentFailure):
       -            self.alice_channel.add_htlc(htlc_dict)
       -        with self.assertRaises(lnutil.RemoteMisbehaving):
       -            self.bob_channel.receive_htlc(htlc_dict)
       -
       -    def part3(self):
       -        # Add a HTLC of 2 BTC to Alice, and the settle it.
       -        # Resulting balances:
       -        #        Alice:        3.0
       -        #        Bob:        7.0
       -        htlc_dict = {
       -            'payment_hash' : paymentHash,
       -            'amount_msat' :  int(2 * one_bitcoin_in_msat),
       -            'cltv_expiry' :  5,
       -        }
       -        alice_idx = self.alice_channel.add_htlc(htlc_dict)
       -        bob_idx = self.bob_channel.receive_htlc(htlc_dict)
       -        force_state_transition(self.alice_channel, self.bob_channel)
       -        self.check_bals(one_bitcoin_in_msat*3\
       -                - self.alice_channel.pending_local_fee(),
       -                  one_bitcoin_in_msat*5)
       -        self.bob_channel.settle_htlc(paymentPreimage, bob_idx)
       -        self.alice_channel.receive_htlc_settle(paymentPreimage, alice_idx)
       -        force_state_transition(self.alice_channel, self.bob_channel)
       -        self.check_bals(one_bitcoin_in_msat*3\
       -                - self.alice_channel.pending_local_fee(),
       -                  one_bitcoin_in_msat*7)
       -        # And now let Bob add an HTLC of 1 BTC. This will take Bob's balance
       -        # all the way down to his channel reserve, but since he is not paying
       -        # the fee this is okay.
       -        htlc_dict['amount_msat'] = one_bitcoin_in_msat
       -        self.bob_channel.add_htlc(htlc_dict)
       -        self.alice_channel.receive_htlc(htlc_dict)
       -        force_state_transition(self.alice_channel, self.bob_channel)
       -        self.check_bals(one_bitcoin_in_msat*3\
       -                - self.alice_channel.pending_local_fee(),
       -                  one_bitcoin_in_msat*6)
       -
       -    def check_bals(self, amt1, amt2):
       -        self.assertEqual(self.alice_channel.available_to_spend(LOCAL), amt1)
       -        self.assertEqual(self.bob_channel.available_to_spend(REMOTE), amt1)
       -        self.assertEqual(self.alice_channel.available_to_spend(REMOTE), amt2)
       -        self.assertEqual(self.bob_channel.available_to_spend(LOCAL), amt2)
       -
       -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
       -        }
       -
       -        old_values = [x.value for x in bob_channel.current_commitment(LOCAL).outputs() ]
       -        aliceHtlcIndex = alice_channel.add_htlc(htlc)
       -        bobHtlcIndex = bob_channel.receive_htlc(htlc)
       -        force_state_transition(alice_channel, bob_channel)
       -        alice_ctx = alice_channel.current_commitment(LOCAL)
       -        bob_ctx = bob_channel.current_commitment(LOCAL)
       -        new_values = [x.value for x in bob_ctx.outputs() ]
       -        self.assertNotEqual(old_values, new_values)
       -        self.assertEqual(len(alice_ctx.outputs()), 3)
       -        self.assertEqual(len(bob_ctx.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.pending_commitment(LOCAL).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_lnchannel.py b/electrum/tests/test_lnchannel.py
       t@@ -0,0 +1,826 @@
       +# Copyright (C) 2018 The Electrum developers
       +# Copyright (C) 2015-2018 The Lightning Network Developers
       +#
       +# Permission is hereby granted, free of charge, to any person obtaining a copy
       +# of this software and associated documentation files (the "Software"), to deal
       +# in the Software without restriction, including without limitation the rights
       +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
       +# copies of the Software, and to permit persons to whom the Software is
       +# furnished to do so, subject to the following conditions:
       +#
       +# The above copyright notice and this permission notice shall be included in
       +# all copies or substantial portions of the Software.
       +#
       +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
       +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
       +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
       +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
       +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
       +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
       +# THE SOFTWARE.
       +
       +import unittest
       +import os
       +import binascii
       +from pprint import pformat
       +
       +from electrum import bitcoin
       +from electrum import lnpeer
       +from electrum import lnchannel
       +from electrum import lnutil
       +from electrum import bip32 as bip32_utils
       +from electrum.lnutil import SENT, LOCAL, REMOTE, RECEIVED
       +from electrum.ecc import sig_string_from_der_sig
       +from electrum.util import set_verbosity
       +
       +one_bitcoin_in_msat = bitcoin.COIN * 1000
       +
       +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, _ = lnpeer.channel_id_from_funding_tx(funding_txid, funding_index)
       +    their_revocation_store = lnpeer.RevocationStore()
       +
       +    return {
       +            "channel_id":channel_id,
       +            "short_channel_id":channel_id[:8],
       +            "funding_outpoint":lnpeer.Outpoint(funding_txid, funding_index),
       +            "remote_config":lnpeer.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=one_bitcoin_in_msat * 5,
       +                max_accepted_htlcs=5,
       +                initial_msat=remote_amount,
       +                ctn = -1,
       +                next_htlc_id = 0,
       +                reserve_sat=0,
       +                htlc_minimum_msat=1,
       +
       +                next_per_commitment_point=nex,
       +                current_per_commitment_point=cur,
       +                revocation_store=their_revocation_store,
       +            ),
       +            "local_config":lnpeer.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=one_bitcoin_in_msat * 5,
       +                max_accepted_htlcs=5,
       +                initial_msat=local_amount,
       +                ctn = 0,
       +                next_htlc_id = 0,
       +                reserve_sat=0,
       +
       +                per_commitment_secret_seed=seed,
       +                funding_locked_received=True,
       +                was_announced=False,
       +                current_commitment_signature=None,
       +                current_htlc_signatures=None,
       +                got_sig_for_next=False,
       +            ),
       +            "constraints":lnpeer.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 = bip32_utils.bip32_root(b"9dk", 'standard')
       +    xprv, xpub = bip32_utils.bip32_private_derivation(xprv, "m/", sequence)
       +    xtype, depth, fingerprint, child_number, c, k = bip32_utils.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(b"\x01"*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(lnutil.privkey_to_pubkey(x), x) for x in alice_raw]
       +    bob_privkeys = [lnutil.Keypair(lnutil.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 = b"\x01" * 32
       +    bob_seed = b"\x02" * 32
       +
       +    alice_first = lnutil.secret_to_pubkey(int.from_bytes(lnutil.get_per_commitment_secret_from_seed(alice_seed, lnutil.RevocationStore.START_INDEX), "big"))
       +    bob_first = lnutil.secret_to_pubkey(int.from_bytes(lnutil.get_per_commitment_secret_from_seed(bob_seed, lnutil.RevocationStore.START_INDEX), "big"))
       +
       +    alice, bob = \
       +        lnchannel.Channel(
       +            create_channel_state(funding_txid, funding_index, funding_sat, feerate, True, local_amount, remote_amount, alice_privkeys, bob_pubkeys, alice_seed, None, bob_first, b"\x02"*33, l_dust=200, r_dust=1300, l_csv=5, r_csv=4), name="alice"), \
       +        lnchannel.Channel(
       +            create_channel_state(funding_txid, funding_index, funding_sat, feerate, False, remote_amount, local_amount, bob_privkeys, alice_pubkeys, bob_seed, None, alice_first, b"\x01"*33, l_dust=1300, r_dust=200, l_csv=4, r_csv=5), name="bob")
       +
       +    alice.set_state('OPEN')
       +    bob.set_state('OPEN')
       +
       +    a_out = alice.current_commitment(LOCAL).outputs()
       +    b_out = bob.pending_commitment(REMOTE).outputs()
       +    assert a_out == b_out, "\n" + pformat((a_out, b_out))
       +
       +    sig_from_bob, a_htlc_sigs = bob.sign_next_commitment()
       +    sig_from_alice, b_htlc_sigs = alice.sign_next_commitment()
       +
       +    assert len(a_htlc_sigs) == 0
       +    assert len(b_htlc_sigs) == 0
       +
       +    alice.config[LOCAL] = alice.config[LOCAL]._replace(current_commitment_signature=sig_from_bob)
       +    bob.config[LOCAL] = bob.config[LOCAL]._replace(current_commitment_signature=sig_from_alice)
       +
       +    alice.set_local_commitment(alice.current_commitment(LOCAL))
       +    bob.set_local_commitment(bob.current_commitment(LOCAL))
       +
       +    alice_second = lnutil.secret_to_pubkey(int.from_bytes(lnutil.get_per_commitment_secret_from_seed(alice_seed, lnutil.RevocationStore.START_INDEX - 1), "big"))
       +    bob_second = lnutil.secret_to_pubkey(int.from_bytes(lnutil.get_per_commitment_secret_from_seed(bob_seed, lnutil.RevocationStore.START_INDEX - 1), "big"))
       +
       +    alice.config[REMOTE] = alice.config[REMOTE]._replace(next_per_commitment_point=bob_second, current_per_commitment_point=bob_first)
       +    bob.config[REMOTE] = bob.config[REMOTE]._replace(next_per_commitment_point=alice_second, current_per_commitment_point=alice_first)
       +
       +    alice.set_remote_commitment()
       +    bob.set_remote_commitment()
       +
       +    alice.remote_commitment_to_be_revoked = alice.remote_commitment
       +    bob.remote_commitment_to_be_revoked = bob.remote_commitment
       +
       +    alice.config[REMOTE] = alice.config[REMOTE]._replace(ctn=0)
       +    bob.config[REMOTE] = bob.config[REMOTE]._replace(ctn=0)
       +
       +    return alice, bob
       +
       +class TestFee(unittest.TestCase):
       +    """
       +    test
       +    https://github.com/lightningnetwork/lightning-rfc/blob/e0c436bd7a3ed6a028e1cb472908224658a14eca/03-transactions.md#requirements-2
       +    """
       +    def test_fee(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):
       +    maxDiff = 999
       +
       +    def assertOutputExistsByValue(self, tx, amt_sat):
       +        for typ, scr, val in tx.outputs():
       +            if val == amt_sat:
       +                break
       +        else:
       +            self.assertFalse()
       +
       +    @staticmethod
       +    def setUpClass():
       +        set_verbosity(True)
       +
       +    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_dict = {
       +            '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_dict)
       +        self.assertNotEqual(self.alice_channel.hm.htlcs_by_direction(REMOTE, RECEIVED, 1), set())
       +
       +        before = self.bob_channel.balance_minus_outgoing_htlcs(REMOTE)
       +        beforeLocal = self.bob_channel.balance_minus_outgoing_htlcs(LOCAL)
       +
       +        self.bobHtlcIndex = self.bob_channel.receive_htlc(self.htlc_dict)
       +
       +        self.assertEqual(1, self.bob_channel.hm.log[LOCAL]['ctn'] + 1)
       +        self.assertNotEqual(self.bob_channel.hm.htlcs_by_direction(LOCAL, RECEIVED, 1), set())
       +        after  = self.bob_channel.balance_minus_outgoing_htlcs(REMOTE)
       +        afterLocal = self.bob_channel.balance_minus_outgoing_htlcs(LOCAL)
       +
       +        self.assertEqual(before - after, self.htlc_dict['amount_msat'])
       +        self.assertEqual(beforeLocal, afterLocal)
       +
       +        self.bob_pending_remote_balance = after
       +
       +        self.htlc = self.bob_channel.hm.log[REMOTE]['adds'][0]
       +
       +    def test_concurrent_reversed_payment(self):
       +        self.htlc_dict['payment_hash'] = bitcoin.sha256(32 * b'\x02')
       +        self.htlc_dict['amount_msat'] += 1000
       +        bob_idx = self.bob_channel.add_htlc(self.htlc_dict)
       +        alice_idx = self.alice_channel.receive_htlc(self.htlc_dict)
       +        self.alice_channel.receive_new_commitment(*self.bob_channel.sign_next_commitment())
       +        self.assertEqual(len(self.alice_channel.pending_commitment(REMOTE).outputs()), 4)
       +
       +    def test_SimpleAddSettleWorkflow(self):
       +        alice_channel, bob_channel = self.alice_channel, self.bob_channel
       +        htlc = self.htlc
       +
       +        alice_out = alice_channel.current_commitment(LOCAL).outputs()
       +        short_idx, = [idx for idx, x in enumerate(alice_out) if len(x.address) == 42]
       +        long_idx,  = [idx for idx, x in enumerate(alice_out) if len(x.address) == 62]
       +        self.assertLess(alice_out[long_idx].value, 5 * 10**8, alice_out)
       +        self.assertEqual(alice_out[short_idx].value, 5 * 10**8, alice_out)
       +
       +        alice_out = alice_channel.current_commitment(REMOTE).outputs()
       +        short_idx, = [idx for idx, x in enumerate(alice_out) if len(x.address) == 42]
       +        long_idx,  = [idx for idx, x in enumerate(alice_out) if len(x.address) == 62]
       +        self.assertLess(alice_out[short_idx].value, 5 * 10**8)
       +        self.assertEqual(alice_out[long_idx].value, 5 * 10**8)
       +
       +        def com():
       +            return alice_channel.local_commitment
       +
       +        self.assertTrue(alice_channel.signature_fits(com()))
       +
       +        self.assertNotEqual(alice_channel.included_htlcs(REMOTE, RECEIVED, 1), [])
       +        self.assertEqual({0: [], 1: [htlc]}, alice_channel.included_htlcs_in_their_latest_ctxs(LOCAL))
       +        self.assertNotEqual(bob_channel.included_htlcs(REMOTE, SENT, 1), [])
       +        self.assertEqual({0: [], 1: [htlc]}, bob_channel.included_htlcs_in_their_latest_ctxs(REMOTE))
       +        self.assertEqual({0: [], 1: []}, alice_channel.included_htlcs_in_their_latest_ctxs(REMOTE))
       +        self.assertEqual({0: [], 1: []}, bob_channel.included_htlcs_in_their_latest_ctxs(LOCAL))
       +
       +        from electrum.lnutil import extract_ctn_from_tx_and_chan
       +        tx0 = str(alice_channel.force_close_tx())
       +        self.assertEqual(alice_channel.config[LOCAL].ctn, 0)
       +        self.assertEqual(extract_ctn_from_tx_and_chan(alice_channel.force_close_tx(), alice_channel), 0)
       +        self.assertTrue(alice_channel.signature_fits(alice_channel.current_commitment(LOCAL)))
       +
       +        # 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")
       +
       +        self.assertTrue(alice_channel.signature_fits(com()))
       +        self.assertEqual(str(alice_channel.current_commitment(LOCAL)), str(com()))
       +
       +        self.assertEqual(next(iter(alice_channel.hm.pending_htlcs(REMOTE)))[0], RECEIVED)
       +        self.assertEqual(alice_channel.hm.pending_htlcs(REMOTE), bob_channel.hm.pending_htlcs(LOCAL))
       +        self.assertEqual(alice_channel.pending_commitment(REMOTE).outputs(), bob_channel.pending_commitment(LOCAL).outputs())
       +
       +        # 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.
       +        self.assertTrue(bob_channel.signature_fits(bob_channel.current_commitment(LOCAL)))
       +        bob_channel.receive_new_commitment(aliceSig, aliceHtlcSigs)
       +        self.assertTrue(bob_channel.signature_fits(bob_channel.pending_commitment(LOCAL)))
       +
       +        self.assertEqual(bob_channel.config[REMOTE].ctn, 0)
       +        self.assertEqual(bob_channel.included_htlcs(REMOTE, SENT, 1), [htlc])
       +
       +        self.assertEqual({0: [], 1: [htlc]}, alice_channel.included_htlcs_in_their_latest_ctxs(LOCAL))
       +        self.assertEqual({0: [], 1: [htlc]}, bob_channel.included_htlcs_in_their_latest_ctxs(REMOTE))
       +        self.assertEqual({0: [], 1: []}, alice_channel.included_htlcs_in_their_latest_ctxs(REMOTE))
       +        self.assertEqual({0: [], 1: []}, bob_channel.included_htlcs_in_their_latest_ctxs(LOCAL))
       +
       +        # 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_channel.serialize()
       +        self.assertTrue(bob_channel.signature_fits(bob_channel.current_commitment(LOCAL)))
       +
       +        # Bob finally sends 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 her signature.
       +        bobSig, bobHtlcSigs = bob_channel.sign_next_commitment()
       +        self.assertTrue(bob_channel.signature_fits(bob_channel.current_commitment(LOCAL)))
       +
       +        self.assertEqual(len(bobHtlcSigs), 1)
       +
       +        self.assertTrue(alice_channel.signature_fits(com()))
       +        self.assertEqual(str(alice_channel.current_commitment(LOCAL)), str(com()))
       +
       +        self.assertEqual(len(alice_channel.pending_commitment(LOCAL).outputs()), 3)
       +
       +        # 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_channel.serialize()
       +        self.assertEqual(alice_channel.remote_commitment.outputs(), alice_channel.current_commitment(REMOTE).outputs())
       +
       +        self.assertTrue(alice_channel.signature_fits(com()))
       +        self.assertTrue(alice_channel.signature_fits(alice_channel.current_commitment(LOCAL)))
       +        alice_channel.serialize()
       +        self.assertEqual(str(alice_channel.current_commitment(LOCAL)), str(com()))
       +
       +        self.assertEqual(len(alice_channel.current_commitment(LOCAL).outputs()), 2)
       +        self.assertEqual(len(alice_channel.current_commitment(REMOTE).outputs()), 3)
       +        self.assertEqual(len(com().outputs()), 2)
       +        self.assertEqual(len(alice_channel.force_close_tx().outputs()), 2)
       +
       +        self.assertEqual(alice_channel.hm.log.keys(), set([LOCAL, REMOTE]))
       +        self.assertEqual(len(alice_channel.hm.log[LOCAL]['adds']), 1)
       +        alice_channel.serialize()
       +
       +        self.assertEqual(alice_channel.pending_commitment(LOCAL).outputs(),
       +                         bob_channel.pending_commitment(REMOTE).outputs())
       +
       +        # 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)
       +        self.assertEqual(alice_channel.remote_commitment.outputs(), alice_channel.current_commitment(REMOTE).outputs())
       +
       +        self.assertEqual(len(alice_channel.current_commitment(REMOTE).outputs()), 3)
       +        self.assertEqual(len(com().outputs()), 3)
       +        self.assertEqual(len(alice_channel.force_close_tx().outputs()), 3)
       +
       +        self.assertEqual(len(alice_channel.hm.log[LOCAL]['adds']), 1)
       +        alice_channel.serialize()
       +
       +        tx1 = str(alice_channel.force_close_tx())
       +        self.assertNotEqual(tx0, tx1)
       +
       +        # Alice then generates a revocation for bob.
       +        self.assertEqual(alice_channel.remote_commitment.outputs(), alice_channel.current_commitment(REMOTE).outputs())
       +        aliceRevocation, _ = alice_channel.revoke_current_commitment()
       +        alice_channel.serialize()
       +        #self.assertEqual(alice_channel.remote_commitment.outputs(), alice_channel.current_commitment(REMOTE).outputs())
       +
       +        tx2 = str(alice_channel.force_close_tx())
       +        # since alice already has the signature for the next one, it doesn't change her force close tx (it was already the newer one)
       +        self.assertEqual(tx1, tx2)
       +
       +        # 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.
       +        self.assertTrue(bob_channel.signature_fits(bob_channel.current_commitment(LOCAL)))
       +        bob_channel.receive_revocation(aliceRevocation)
       +        bob_channel.serialize()
       +
       +        # 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.
       +        alice_ctx = alice_channel.pending_commitment(LOCAL)
       +        bob_ctx = bob_channel.pending_commitment(LOCAL)
       +        self.assertEqual(len(alice_ctx.outputs()), 3, "alice should have three commitment outputs, instead have %s"% len(alice_ctx.outputs()))
       +        self.assertEqual(len(bob_ctx.outputs()), 3, "bob should have three commitment outputs, instead have %s"% len(bob_ctx.outputs()))
       +        self.assertOutputExistsByValue(alice_ctx, htlc.amount_msat // 1000)
       +        self.assertOutputExistsByValue(bob_ctx, 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)
       +
       +        #self.assertEqual(alice_channel.remote_commitment.outputs(), alice_channel.current_commitment(REMOTE).outputs())
       +        alice_channel.receive_htlc_settle(preimage, self.aliceHtlcIndex)
       +
       +        tx3 = str(alice_channel.force_close_tx())
       +        # just settling a htlc does not change her force close tx
       +        self.assertEqual(tx2, tx3)
       +
       +        bobSig2, bobHtlcSigs2 = bob_channel.sign_next_commitment()
       +        self.assertEqual(len(bobHtlcSigs2), 0)
       +
       +        self.assertEqual(alice_channel.hm.htlcs_by_direction(REMOTE, RECEIVED), [htlc])
       +        self.assertEqual(alice_channel.included_htlcs(REMOTE, RECEIVED, alice_channel.config[REMOTE].ctn), [htlc])
       +        self.assertEqual({1: [htlc], 2: []}, alice_channel.included_htlcs_in_their_latest_ctxs(LOCAL))
       +        self.assertEqual({1: [htlc], 2: []}, bob_channel.included_htlcs_in_their_latest_ctxs(REMOTE))
       +        self.assertEqual({1: [], 2: []}, alice_channel.included_htlcs_in_their_latest_ctxs(REMOTE))
       +        self.assertEqual({1: [], 2: []}, bob_channel.included_htlcs_in_their_latest_ctxs(LOCAL))
       +
       +        alice_ctx_bob_version = bob_channel.pending_commitment(REMOTE).outputs()
       +        alice_ctx_alice_version = alice_channel.pending_commitment(LOCAL).outputs()
       +        self.assertEqual(alice_ctx_alice_version, alice_ctx_bob_version)
       +
       +        alice_channel.receive_new_commitment(bobSig2, bobHtlcSigs2)
       +
       +        tx4 = str(alice_channel.force_close_tx())
       +        self.assertNotEqual(tx3, tx4)
       +
       +        self.assertEqual(alice_channel.balance(LOCAL), 500000000000)
       +        self.assertEqual(1, alice_channel.config[LOCAL].ctn)
       +        self.assertEqual(len(alice_channel.included_htlcs(LOCAL, RECEIVED, ctn=2)), 0)
       +        aliceRevocation2, _ = alice_channel.revoke_current_commitment()
       +        alice_channel.serialize()
       +        aliceSig2, aliceHtlcSigs2 = alice_channel.sign_next_commitment()
       +        self.assertEqual(aliceHtlcSigs2, [], "alice should generate no htlc signatures")
       +        self.assertEqual(len(bob_channel.current_commitment(LOCAL).outputs()), 3)
       +        self.assertEqual(len(bob_channel.pending_commitment(LOCAL).outputs()), 2)
       +        received, sent = bob_channel.receive_revocation(aliceRevocation2)
       +        bob_channel.serialize()
       +        self.assertEqual(received, one_bitcoin_in_msat)
       +
       +        bob_channel.receive_new_commitment(aliceSig2, aliceHtlcSigs2)
       +
       +        bobRevocation2, _ = bob_channel.revoke_current_commitment()
       +        bob_channel.serialize()
       +        alice_channel.receive_revocation(bobRevocation2)
       +        alice_channel.serialize()
       +
       +        # 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")
       +
       +        self.assertEqual(self.bob_pending_remote_balance, self.alice_channel.balance(LOCAL))
       +
       +        alice_channel.update_fee(100000, True)
       +        alice_outputs = alice_channel.pending_commitment(REMOTE).outputs()
       +        old_outputs = bob_channel.pending_commitment(LOCAL).outputs()
       +        bob_channel.update_fee(100000, False)
       +        new_outputs = bob_channel.pending_commitment(LOCAL).outputs()
       +        self.assertNotEqual(old_outputs, new_outputs)
       +        self.assertEqual(alice_outputs, new_outputs)
       +
       +        tx5 = str(alice_channel.force_close_tx())
       +        # sending a fee update does not change her force close tx
       +        self.assertEqual(tx4, tx5)
       +
       +        force_state_transition(alice_channel, bob_channel)
       +
       +        tx6 = str(alice_channel.force_close_tx())
       +        self.assertNotEqual(tx5, tx6)
       +
       +        self.htlc_dict['amount_msat'] *= 5
       +        bob_index = bob_channel.add_htlc(self.htlc_dict)
       +        alice_index = alice_channel.receive_htlc(self.htlc_dict)
       +
       +        bob_channel.pending_commitment(REMOTE)
       +        alice_channel.pending_commitment(LOCAL)
       +
       +        alice_channel.pending_commitment(REMOTE)
       +        bob_channel.pending_commitment(LOCAL)
       +
       +        force_state_transition(bob_channel, alice_channel)
       +        alice_channel.settle_htlc(self.paymentPreimage, alice_index)
       +        bob_channel.receive_htlc_settle(self.paymentPreimage, bob_index)
       +        force_state_transition(bob_channel, alice_channel)
       +        self.assertEqual(alice_channel.total_msat(SENT), one_bitcoin_in_msat, "alice satoshis sent incorrect")
       +        self.assertEqual(alice_channel.total_msat(RECEIVED), 5 * one_bitcoin_in_msat, "alice satoshis received incorrect")
       +        self.assertEqual(bob_channel.total_msat(RECEIVED), one_bitcoin_in_msat, "bob satoshis received incorrect")
       +        self.assertEqual(bob_channel.total_msat(SENT), 5 * one_bitcoin_in_msat, "bob satoshis sent incorrect")
       +
       +        alice_channel.serialize()
       +
       +
       +    def alice_to_bob_fee_update(self, fee=111):
       +        aoldctx = self.alice_channel.pending_commitment(REMOTE).outputs()
       +        self.alice_channel.update_fee(fee, True)
       +        anewctx = self.alice_channel.pending_commitment(REMOTE).outputs()
       +        self.assertNotEqual(aoldctx, anewctx)
       +        boldctx = self.bob_channel.pending_commitment(LOCAL).outputs()
       +        self.bob_channel.update_fee(fee, False)
       +        bnewctx = self.bob_channel.pending_commitment(LOCAL).outputs()
       +        self.assertNotEqual(boldctx, bnewctx)
       +        self.assertEqual(anewctx, bnewctx)
       +        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)
       +
       +    def test_AddHTLCNegativeBalance(self):
       +        # the test in lnd doesn't set the fee to zero.
       +        # probably lnd subtracts commitment fee after deciding weather
       +        # an htlc can be added. so we set the fee to zero so that
       +        # the test can work.
       +        self.alice_to_bob_fee_update(0)
       +        force_state_transition(self.alice_channel, self.bob_channel)
       +
       +        self.htlc_dict['payment_hash'] = bitcoin.sha256(32 * b'\x02')
       +        self.alice_channel.add_htlc(self.htlc_dict)
       +        self.htlc_dict['payment_hash'] = bitcoin.sha256(32 * b'\x03')
       +        self.alice_channel.add_htlc(self.htlc_dict)
       +        # now there are three htlcs (one was in setUp)
       +
       +        # Alice now has an available balance of 2 BTC. We'll add a new HTLC of
       +        # value 2 BTC, which should make Alice's balance negative (since she
       +        # has to pay a commitment fee).
       +        new = dict(self.htlc_dict)
       +        new['amount_msat'] *= 2.5
       +        new['payment_hash'] = bitcoin.sha256(32 * b'\x04')
       +        with self.assertRaises(lnutil.PaymentFailure) as cm:
       +            self.alice_channel.add_htlc(new)
       +        self.assertIn('Not enough local balance', cm.exception.args[0])
       +
       +    def test_sign_commitment_is_pure(self):
       +        force_state_transition(self.alice_channel, self.bob_channel)
       +        self.htlc_dict['payment_hash'] = bitcoin.sha256(b'\x02' * 32)
       +        aliceHtlcIndex = self.alice_channel.add_htlc(self.htlc_dict)
       +        before_signing = self.alice_channel.to_save()
       +        self.alice_channel.sign_next_commitment()
       +        after_signing = self.alice_channel.to_save()
       +        try:
       +            self.assertEqual(before_signing, after_signing)
       +        except:
       +            try:
       +                from deepdiff import DeepDiff
       +            except ImportError:
       +                raise
       +            raise Exception(pformat(DeepDiff(before_signing, after_signing)))
       +
       +class TestAvailableToSpend(unittest.TestCase):
       +    def test_DesyncHTLCs(self):
       +        alice_channel, bob_channel = create_test_channels()
       +
       +        paymentPreimage = b"\x01" * 32
       +        paymentHash = bitcoin.sha256(paymentPreimage)
       +        htlc_dict = {
       +            'payment_hash' : paymentHash,
       +            'amount_msat' :  int(4.1 * one_bitcoin_in_msat),
       +            'cltv_expiry' :  5,
       +        }
       +
       +        alice_idx = alice_channel.add_htlc(htlc_dict)
       +        bob_idx = bob_channel.receive_htlc(htlc_dict)
       +        force_state_transition(alice_channel, bob_channel)
       +        bob_channel.fail_htlc(bob_idx)
       +        alice_channel.receive_fail_htlc(alice_idx)
       +        # Alice now has gotten all her original balance (5 BTC) back, however,
       +        # adding a new HTLC at this point SHOULD fail, since if she adds the
       +        # HTLC and signs the next state, Bob cannot assume she received the
       +        # FailHTLC, and must assume she doesn't have the necessary balance
       +        # available.
       +        # We try adding an HTLC of value 1 BTC, which should fail because the
       +        # balance is unavailable.
       +        htlc_dict = {
       +            'payment_hash' : paymentHash,
       +            'amount_msat' :  one_bitcoin_in_msat,
       +            'cltv_expiry' :  5,
       +        }
       +        with self.assertRaises(lnutil.PaymentFailure):
       +            alice_channel.add_htlc(htlc_dict)
       +        # Now do a state transition, which will ACK the FailHTLC, making Alice
       +        # able to add the new HTLC.
       +        force_state_transition(alice_channel, bob_channel)
       +        alice_channel.add_htlc(htlc_dict)
       +
       +class TestChanReserve(unittest.TestCase):
       +    def setUp(self):
       +        alice_channel, bob_channel = create_test_channels()
       +        alice_min_reserve = int(.5 * one_bitcoin_in_msat // 1000)
       +        # We set Bob's channel reserve to a value that is larger than
       +        # his current balance in the channel. This will ensure that
       +        # after a channel is first opened, Bob can still receive HTLCs
       +        # even though his balance is less than his channel reserve.
       +        bob_min_reserve = 6 * one_bitcoin_in_msat // 1000
       +        # bob min reserve was decided by alice, but applies to bob
       +
       +        alice_channel.config[LOCAL] =\
       +            alice_channel.config[LOCAL]._replace(reserve_sat=bob_min_reserve)
       +        alice_channel.config[REMOTE] =\
       +            alice_channel.config[REMOTE]._replace(reserve_sat=alice_min_reserve)
       +
       +        bob_channel.config[LOCAL] =\
       +            bob_channel.config[LOCAL]._replace(reserve_sat=alice_min_reserve)
       +        bob_channel.config[REMOTE] =\
       +            bob_channel.config[REMOTE]._replace(reserve_sat=bob_min_reserve)
       +
       +        self.alice_channel = alice_channel
       +        self.bob_channel = bob_channel
       +
       +    def test_part1(self):
       +        # Add an HTLC that will increase Bob's balance. This should succeed,
       +        # since Alice stays above her channel reserve, and Bob increases his
       +        # balance (while still being below his channel reserve).
       +        #
       +        # Resulting balances:
       +        #        Alice:        4.5
       +        #        Bob:        5.0
       +        paymentPreimage = b"\x01" * 32
       +        paymentHash = bitcoin.sha256(paymentPreimage)
       +        htlc_dict = {
       +            'payment_hash' : paymentHash,
       +            'amount_msat' :  int(.5 * one_bitcoin_in_msat),
       +            'cltv_expiry' :  5,
       +        }
       +        self.alice_channel.add_htlc(htlc_dict)
       +        self.bob_channel.receive_htlc(htlc_dict)
       +        # Force a state transition, making sure this HTLC is considered valid
       +        # even though the channel reserves are not met.
       +        force_state_transition(self.alice_channel, self.bob_channel)
       +
       +        aliceSelfBalance = self.alice_channel.balance(LOCAL)\
       +                - lnchannel.htlcsum(self.alice_channel.hm.htlcs_by_direction(LOCAL, SENT))
       +        bobBalance = self.bob_channel.balance(REMOTE)\
       +                - lnchannel.htlcsum(self.alice_channel.hm.htlcs_by_direction(REMOTE, SENT))
       +        self.assertEqual(aliceSelfBalance, one_bitcoin_in_msat*4.5)
       +        self.assertEqual(bobBalance, one_bitcoin_in_msat*5)
       +        # Now let Bob try to add an HTLC. This should fail, since it will
       +        # decrease his balance, which is already below the channel reserve.
       +        #
       +        # Resulting balances:
       +        #        Alice:        4.5
       +        #        Bob:        5.0
       +        with self.assertRaises(lnutil.PaymentFailure):
       +            htlc_dict['payment_hash'] = bitcoin.sha256(32 * b'\x02')
       +            self.bob_channel.add_htlc(htlc_dict)
       +        with self.assertRaises(lnutil.RemoteMisbehaving):
       +            self.alice_channel.receive_htlc(htlc_dict)
       +
       +    def part2(self):
       +        paymentPreimage = b"\x01" * 32
       +        paymentHash = bitcoin.sha256(paymentPreimage)
       +        # Now we'll add HTLC of 3.5 BTC to Alice's commitment, this should put
       +        # Alice's balance at 1.5 BTC.
       +        #
       +        # Resulting balances:
       +        #        Alice:        1.5
       +        #        Bob:        9.5
       +        htlc_dict = {
       +            'payment_hash' : paymentHash,
       +            'amount_msat' :  int(3.5 * one_bitcoin_in_msat),
       +            'cltv_expiry' :  5,
       +        }
       +        self.alice_channel.add_htlc(htlc_dict)
       +        self.bob_channel.receive_htlc(htlc_dict)
       +        # Add a second HTLC of 1 BTC. This should fail because it will take
       +        # Alice's balance all the way down to her channel reserve, but since
       +        # she is the initiator the additional transaction fee makes her
       +        # balance dip below.
       +        htlc_dict['amount_msat'] = one_bitcoin_in_msat
       +        with self.assertRaises(lnutil.PaymentFailure):
       +            self.alice_channel.add_htlc(htlc_dict)
       +        with self.assertRaises(lnutil.RemoteMisbehaving):
       +            self.bob_channel.receive_htlc(htlc_dict)
       +
       +    def part3(self):
       +        # Add a HTLC of 2 BTC to Alice, and the settle it.
       +        # Resulting balances:
       +        #        Alice:        3.0
       +        #        Bob:        7.0
       +        htlc_dict = {
       +            'payment_hash' : paymentHash,
       +            'amount_msat' :  int(2 * one_bitcoin_in_msat),
       +            'cltv_expiry' :  5,
       +        }
       +        alice_idx = self.alice_channel.add_htlc(htlc_dict)
       +        bob_idx = self.bob_channel.receive_htlc(htlc_dict)
       +        force_state_transition(self.alice_channel, self.bob_channel)
       +        self.check_bals(one_bitcoin_in_msat*3\
       +                - self.alice_channel.pending_local_fee(),
       +                  one_bitcoin_in_msat*5)
       +        self.bob_channel.settle_htlc(paymentPreimage, bob_idx)
       +        self.alice_channel.receive_htlc_settle(paymentPreimage, alice_idx)
       +        force_state_transition(self.alice_channel, self.bob_channel)
       +        self.check_bals(one_bitcoin_in_msat*3\
       +                - self.alice_channel.pending_local_fee(),
       +                  one_bitcoin_in_msat*7)
       +        # And now let Bob add an HTLC of 1 BTC. This will take Bob's balance
       +        # all the way down to his channel reserve, but since he is not paying
       +        # the fee this is okay.
       +        htlc_dict['amount_msat'] = one_bitcoin_in_msat
       +        self.bob_channel.add_htlc(htlc_dict)
       +        self.alice_channel.receive_htlc(htlc_dict)
       +        force_state_transition(self.alice_channel, self.bob_channel)
       +        self.check_bals(one_bitcoin_in_msat*3\
       +                - self.alice_channel.pending_local_fee(),
       +                  one_bitcoin_in_msat*6)
       +
       +    def check_bals(self, amt1, amt2):
       +        self.assertEqual(self.alice_channel.available_to_spend(LOCAL), amt1)
       +        self.assertEqual(self.bob_channel.available_to_spend(REMOTE), amt1)
       +        self.assertEqual(self.alice_channel.available_to_spend(REMOTE), amt2)
       +        self.assertEqual(self.bob_channel.available_to_spend(LOCAL), amt2)
       +
       +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
       +        }
       +
       +        old_values = [x.value for x in bob_channel.current_commitment(LOCAL).outputs() ]
       +        aliceHtlcIndex = alice_channel.add_htlc(htlc)
       +        bobHtlcIndex = bob_channel.receive_htlc(htlc)
       +        force_state_transition(alice_channel, bob_channel)
       +        alice_ctx = alice_channel.current_commitment(LOCAL)
       +        bob_ctx = bob_channel.current_commitment(LOCAL)
       +        new_values = [x.value for x in bob_ctx.outputs() ]
       +        self.assertNotEqual(old_values, new_values)
       +        self.assertEqual(len(alice_ctx.outputs()), 3)
       +        self.assertEqual(len(bob_ctx.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.pending_commitment(LOCAL).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_lnpeer.py b/electrum/tests/test_lnpeer.py
       t@@ -0,0 +1,252 @@
       +import unittest
       +import asyncio
       +import tempfile
       +from decimal import Decimal
       +import os
       +from contextlib import contextmanager
       +from collections import defaultdict
       +
       +from electrum.network import Network
       +from electrum.ecc import ECPrivkey
       +from electrum import simple_config, lnutil
       +from electrum.lnaddr import lnencode, LnAddr, lndecode
       +from electrum.bitcoin import COIN, sha256
       +from electrum.util import bh2u
       +
       +from electrum.lnpeer import Peer
       +from electrum.lnutil import LNPeerAddr, Keypair, privkey_to_pubkey
       +from electrum.lnutil import LightningPeerConnectionClosed, RemoteMisbehaving
       +from electrum.lnutil import PaymentFailure
       +from electrum.lnrouter import ChannelDB, LNPathFinder
       +from electrum.lnworker import LNWorker
       +from electrum.lnmsg import encode_msg, decode_msg
       +
       +from .test_lnchannel import create_test_channels
       +
       +def keypair():
       +    priv = ECPrivkey.generate_random_key().get_secret_bytes()
       +    k1 = Keypair(
       +            pubkey=privkey_to_pubkey(priv),
       +            privkey=priv)
       +    return k1
       +
       +@contextmanager
       +def noop_lock():
       +    yield
       +
       +class MockNetwork:
       +    def __init__(self, tx_queue):
       +        self.callbacks = defaultdict(list)
       +        self.lnwatcher = None
       +        user_config = {}
       +        user_dir = tempfile.mkdtemp(prefix="electrum-lnpeer-test-")
       +        self.config = simple_config.SimpleConfig(user_config, read_user_dir_function=lambda: user_dir)
       +        self.asyncio_loop = asyncio.get_event_loop()
       +        self.channel_db = ChannelDB(self)
       +        self.interface = None
       +        self.path_finder = LNPathFinder(self.channel_db)
       +        self.tx_queue = tx_queue
       +
       +    @property
       +    def callback_lock(self):
       +        return noop_lock()
       +
       +    register_callback = Network.register_callback
       +    unregister_callback = Network.unregister_callback
       +    trigger_callback = Network.trigger_callback
       +
       +    def get_local_height(self):
       +        return 0
       +
       +    async def broadcast_transaction(self, tx):
       +        if self.tx_queue:
       +            await self.tx_queue.put(tx)
       +
       +class MockStorage:
       +    def put(self, key, value):
       +        pass
       +
       +    def get(self, key, default=None):
       +        pass
       +
       +    def write(self):
       +        pass
       +
       +class MockWallet:
       +    storage = MockStorage()
       +
       +class MockLNWorker:
       +    def __init__(self, remote_keypair, local_keypair, chan, tx_queue):
       +        self.chan = chan
       +        self.remote_keypair = remote_keypair
       +        self.node_keypair = local_keypair
       +        self.network = MockNetwork(tx_queue)
       +        self.channels = {self.chan.channel_id: self.chan}
       +        self.invoices = {}
       +        self.inflight = {}
       +        self.wallet = MockWallet()
       +
       +    @property
       +    def lock(self):
       +        return noop_lock()
       +
       +    @property
       +    def peers(self):
       +        return {self.remote_keypair.pubkey: self.peer}
       +
       +    def channels_for_peer(self, pubkey):
       +        return self.channels
       +
       +    def get_channel_by_short_id(self, short_channel_id):
       +        with self.lock:
       +            for chan in self.channels.values():
       +                if chan.short_channel_id == short_channel_id:
       +                    return chan
       +
       +    def save_channel(self, chan):
       +        print("Ignoring channel save")
       +
       +    def on_channels_updated(self):
       +        pass
       +
       +    def save_invoice(*args):
       +        pass
       +
       +    get_invoice = LNWorker.get_invoice
       +    _create_route_from_invoice = LNWorker._create_route_from_invoice
       +    _check_invoice = staticmethod(LNWorker._check_invoice)
       +    _pay_to_route = LNWorker._pay_to_route
       +    force_close_channel = LNWorker.force_close_channel
       +    get_first_timestamp = lambda self: 0
       +
       +class MockTransport:
       +    def __init__(self):
       +        self.queue = asyncio.Queue()
       +
       +    def name(self):
       +        return ""
       +
       +    async def read_messages(self):
       +        while True:
       +            yield await self.queue.get()
       +
       +class NoFeaturesTransport(MockTransport):
       +    """
       +    This answers the init message with a init that doesn't signal any features.
       +    Used for testing that we require DATA_LOSS_PROTECT.
       +    """
       +    def send_bytes(self, data):
       +        decoded = decode_msg(data)
       +        print(decoded)
       +        if decoded[0] == 'init':
       +            self.queue.put_nowait(encode_msg('init', lflen=1, gflen=1, localfeatures=b"\x00", globalfeatures=b"\x00"))
       +
       +class PutIntoOthersQueueTransport(MockTransport):
       +    def __init__(self):
       +        super().__init__()
       +        self.other_mock_transport = None
       +
       +    def send_bytes(self, data):
       +        self.other_mock_transport.queue.put_nowait(data)
       +
       +def transport_pair():
       +    t1 = PutIntoOthersQueueTransport()
       +    t2 = PutIntoOthersQueueTransport()
       +    t1.other_mock_transport = t2
       +    t2.other_mock_transport = t1
       +    return t1, t2
       +
       +class TestPeer(unittest.TestCase):
       +    def setUp(self):
       +        self.alice_channel, self.bob_channel = create_test_channels()
       +
       +    def test_require_data_loss_protect(self):
       +        mock_lnworker = MockLNWorker(keypair(), keypair(), self.alice_channel, tx_queue=None)
       +        mock_transport = NoFeaturesTransport()
       +        p1 = Peer(mock_lnworker, b"\x00" * 33, mock_transport, request_initial_sync=False)
       +        mock_lnworker.peer = p1
       +        with self.assertRaises(LightningPeerConnectionClosed):
       +            run(asyncio.wait_for(p1._main_loop(), 1))
       +
       +    def prepare_peers(self):
       +        k1, k2 = keypair(), keypair()
       +        t1, t2 = transport_pair()
       +        q1, q2 = asyncio.Queue(), asyncio.Queue()
       +        w1 = MockLNWorker(k1, k2, self.alice_channel, tx_queue=q1)
       +        w2 = MockLNWorker(k2, k1, self.bob_channel, tx_queue=q2)
       +        p1 = Peer(w1, k1.pubkey, t1, request_initial_sync=False)
       +        p2 = Peer(w2, k2.pubkey, t2, request_initial_sync=False)
       +        w1.peer = p1
       +        w2.peer = p2
       +        # mark_open won't work if state is already OPEN.
       +        # so set it to OPENING
       +        self.alice_channel.set_state("OPENING")
       +        self.bob_channel.set_state("OPENING")
       +        # this populates the channel graph:
       +        p1.mark_open(self.alice_channel)
       +        p2.mark_open(self.bob_channel)
       +        return p1, p2, w1, w2, q1, q2
       +
       +    @staticmethod
       +    def prepare_invoice(w2 # receiver
       +            ):
       +        amount_btc = 100000/Decimal(COIN)
       +        payment_preimage = os.urandom(32)
       +        RHASH = sha256(payment_preimage)
       +        addr = LnAddr(
       +                    RHASH,
       +                    amount_btc,
       +                    tags=[('c', lnutil.MIN_FINAL_CLTV_EXPIRY_FOR_INVOICE),
       +                          ('d', 'coffee')
       +                         ])
       +        pay_req = lnencode(addr, w2.node_keypair.privkey)
       +        w2.invoices[bh2u(RHASH)] = (bh2u(payment_preimage), pay_req, True, None)
       +        return pay_req
       +
       +    @staticmethod
       +    def prepare_ln_message_future(w2 # receiver
       +            ):
       +        fut = asyncio.Future()
       +        def evt_set(event, _lnworker, msg, _htlc_id):
       +            fut.set_result(msg)
       +        w2.network.register_callback(evt_set, ['ln_message'])
       +        return fut
       +
       +    def test_payment(self):
       +        p1, p2, w1, w2, _q1, _q2 = self.prepare_peers()
       +        pay_req = self.prepare_invoice(w2)
       +        fut = self.prepare_ln_message_future(w2)
       +
       +        async def pay():
       +            addr, peer, coro = LNWorker._pay(w1, pay_req)
       +            await coro
       +            print("HTLC ADDED")
       +            self.assertEqual(await fut, 'Payment received')
       +            gath.cancel()
       +        gath = asyncio.gather(pay(), p1._main_loop(), p2._main_loop())
       +        with self.assertRaises(asyncio.CancelledError):
       +            run(gath)
       +
       +    def test_channel_usage_after_closing(self):
       +        p1, p2, w1, w2, q1, q2 = self.prepare_peers()
       +        pay_req = self.prepare_invoice(w2)
       +
       +        addr = w1._check_invoice(pay_req)
       +        route = w1._create_route_from_invoice(decoded_invoice=addr)
       +
       +        run(w1.force_close_channel(self.alice_channel.channel_id))
       +        # check if a tx (commitment transaction) was broadcasted:
       +        assert q1.qsize() == 1
       +
       +        with self.assertRaises(PaymentFailure) as e:
       +            w1._create_route_from_invoice(decoded_invoice=addr)
       +        self.assertEqual(str(e.exception), 'No path found')
       +
       +        peer = w1.peers[route[0].node_id]
       +        # AssertionError is ok since we shouldn't use old routes, and the
       +        # route finding should fail when channel is closed
       +        with self.assertRaises(AssertionError):
       +            run(asyncio.gather(w1._pay_to_route(route, addr, pay_req), p1._main_loop(), p2._main_loop()))
       +
       +def run(coro):
       +    asyncio.get_event_loop().run_until_complete(coro)