URI: 
       tlightning: fixup after rebasing on restructured master - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
   DIR commit 35adc3231b297d03ad0a0534d65665ad14c0d9f6
   DIR parent 1db7a8334afc8b2b60c7b87be1a12917439c1549
  HTML Author: Janus <ysangkok@gmail.com>
       Date:   Fri, 13 Jul 2018 17:05:04 +0200
       
       lightning: fixup after rebasing on restructured master
       
       Diffstat:
         A electrum/gui/kivy/uix/dialogs/ligh… |     123 +++++++++++++++++++++++++++++++
         A electrum/gui/kivy/uix/dialogs/ligh… |      93 +++++++++++++++++++++++++++++++
         R gui/qt/channels_list.py -> electru… |       0 
         R lib/lightning.json -> electrum/lig… |       0 
         R lib/lnaddr.py -> electrum/lnaddr.py |       0 
         R lib/lnbase.py -> electrum/lnbase.py |       0 
         R lib/lnhtlc.py -> electrum/lnhtlc.py |       0 
         R lib/lnrouter.py -> electrum/lnrout… |       0 
         R lib/lnutil.py -> electrum/lnutil.py |       0 
         R lib/lnwatcher.py -> electrum/lnwat… |       0 
         R lib/lnworker.py -> electrum/lnwork… |       0 
         A electrum/tests/test_bolt11.py       |      97 ++++++++++++++++++++++++++++++
         A electrum/tests/test_lnhtlc.py       |     344 ++++++++++++++++++++++++++++++
         A electrum/tests/test_lnrouter.py     |     151 +++++++++++++++++++++++++++++++
         A electrum/tests/test_lnutil.py       |     677 +++++++++++++++++++++++++++++++
         D gui/kivy/uix/dialogs/lightning_cha… |     123 -------------------------------
         D gui/kivy/uix/dialogs/lightning_pay… |      93 -------------------------------
         D lib/tests/test_bolt11.py            |      97 ------------------------------
         D lib/tests/test_lnhtlc.py            |     344 ------------------------------
         D lib/tests/test_lnrouter.py          |     151 -------------------------------
         D lib/tests/test_lnutil.py            |     677 -------------------------------
       
       21 files changed, 1485 insertions(+), 1485 deletions(-)
       ---
   DIR diff --git a/electrum/gui/kivy/uix/dialogs/lightning_channels.py b/electrum/gui/kivy/uix/dialogs/lightning_channels.py
       t@@ -0,0 +1,123 @@
       +import binascii
       +from kivy.lang import Builder
       +from kivy.factory import Factory
       +from kivy.uix.popup import Popup
       +from kivy.clock import Clock
       +from electrum.gui.kivy.uix.context_menu import ContextMenu
       +
       +Builder.load_string('''
       +<LightningChannelItem@CardItem>
       +    details: {}
       +    active: False
       +    channelId: '<channelId not set>'
       +    Label:
       +        text: root.channelId
       +
       +<LightningChannelsDialog@Popup>:
       +    name: 'lightning_channels'
       +    BoxLayout:
       +        id: box
       +        orientation: 'vertical'
       +        spacing: '1dp'
       +        ScrollView:
       +            GridLayout:
       +                cols: 1
       +                id: lightning_channels_container
       +                size_hint: 1, None
       +                height: self.minimum_height
       +                spacing: '2dp'
       +                padding: '12dp'
       +
       +<ChannelDetailsItem@BoxLayout>:
       +    canvas.before:
       +        Color:
       +            rgba: 0.5, 0.5, 0.5, 1
       +        Rectangle:
       +            size: self.size
       +            pos: self.pos
       +    value: ''
       +    Label:
       +        text: root.value
       +        text_size: self.size # this makes the text not overflow, but wrap
       +
       +<ChannelDetailsRow@BoxLayout>:
       +    keyName: ''
       +    value: ''
       +    ChannelDetailsItem:
       +        value: root.keyName
       +        size_hint_x: 0.5 # this makes the column narrower
       +
       +    # see https://blog.kivy.org/2014/07/wrapping-text-in-kivys-label/
       +    ScrollView:
       +        Label:
       +            text: root.value
       +            size_hint_y: None
       +            text_size: self.width, None
       +            height: self.texture_size[1]
       +
       +<ChannelDetailsList@RecycleView>:
       +    scroll_type: ['bars', 'content']
       +    scroll_wheel_distance: dp(114)
       +    bar_width: dp(10)
       +    viewclass: 'ChannelDetailsRow'
       +    RecycleBoxLayout:
       +        default_size: None, dp(56)
       +        default_size_hint: 1, None
       +        size_hint_y: None
       +        height: self.minimum_height
       +        orientation: 'vertical'
       +        spacing: dp(2)
       +
       +<ChannelDetailsPopup@Popup>:
       +    id: popuproot
       +    data: []
       +    ChannelDetailsList:
       +        data: popuproot.data
       +''')
       +
       +class ChannelDetailsPopup(Popup):
       +    def __init__(self, data, **kwargs):
       +        super(ChanenlDetailsPopup,self).__init__(**kwargs)
       +        self.data = data
       +
       +class LightningChannelsDialog(Factory.Popup):
       +    def __init__(self, app):
       +        super(LightningChannelsDialog, self).__init__()
       +        self.clocks = []
       +        self.app = app
       +        self.context_menu = None
       +        self.app.wallet.lnworker.subscribe_channel_list_updates_from_other_thread(self.rpc_result_handler)
       +
       +    def show_channel_details(self, obj):
       +        p = Factory.ChannelDetailsPopup()
       +        p.data = [{'keyName': key, 'value': str(obj.details[key])} for key in obj.details.keys()]
       +        p.open()
       +
       +    def close_channel(self, obj):
       +        print("UNIMPLEMENTED asked to close channel", obj.channelId) # TODO
       +
       +    def show_menu(self, obj):
       +        self.hide_menu()
       +        self.context_menu = ContextMenu(obj, [("Close", self.close_channel),
       +            ("Details", self.show_channel_details)])
       +        self.ids.box.add_widget(self.context_menu)
       +
       +    def hide_menu(self):
       +        if self.context_menu is not None:
       +            self.ids.box.remove_widget(self.context_menu)
       +            self.context_menu = None
       +
       +    def rpc_result_handler(self, res):
       +        channel_cards = self.ids.lightning_channels_container
       +        channel_cards.clear_widgets()
       +        if "channels" in res:
       +          for i in res["channels"]:
       +            item = Factory.LightningChannelItem()
       +            item.screen = self
       +            print(i)
       +            item.channelId = i["chan_id"]
       +            item.active = i["active"]
       +            item.details = i
       +            channel_cards.add_widget(item)
       +        else:
       +          self.app.show_info(res)
   DIR diff --git a/electrum/gui/kivy/uix/dialogs/lightning_payer.py b/electrum/gui/kivy/uix/dialogs/lightning_payer.py
       t@@ -0,0 +1,93 @@
       +import binascii
       +from kivy.lang import Builder
       +from kivy.factory import Factory
       +from electrum.gui.kivy.i18n import _
       +from kivy.clock import mainthread
       +from electrum.lnaddr import lndecode
       +
       +Builder.load_string('''
       +<LightningPayerDialog@Popup>
       +    id: s
       +    name: 'lightning_payer'
       +    invoice_data: ''
       +    BoxLayout:
       +        orientation: "vertical"
       +        BlueButton:
       +            text: s.invoice_data if s.invoice_data else _('Lightning invoice')
       +            shorten: True
       +            on_release: Clock.schedule_once(lambda dt: app.show_info(_('Copy and paste the lightning invoice using the Paste button, or use the camera to scan a QR code.')))
       +        GridLayout:
       +            cols: 4
       +            size_hint: 1, None
       +            height: '48dp'
       +            IconButton:
       +                id: qr
       +                on_release: Clock.schedule_once(lambda dt: app.scan_qr(on_complete=s.on_lightning_qr))
       +                icon: 'atlas://gui/kivy/theming/light/camera'
       +            Button:
       +                text: _('Paste')
       +                on_release: s.do_paste()
       +            Button:
       +                text: _('Paste using xclip')
       +                on_release: s.do_paste_xclip()
       +            Button:
       +                text: _('Clear')
       +                on_release: s.do_clear()
       +        Button:
       +            size_hint: 1, None
       +            height: '48dp'
       +            text: _('Open channel to pubkey in invoice')
       +            on_release: s.do_open_channel()
       +        Button:
       +            size_hint: 1, None
       +            height: '48dp'
       +            text: _('Pay pasted/scanned invoice')
       +            on_release: s.do_pay()
       +''')
       +
       +class LightningPayerDialog(Factory.Popup):
       +    def __init__(self, app):
       +        super(LightningPayerDialog, self).__init__()
       +        self.app = app
       +
       +    #def open(self, *args, **kwargs):
       +    #    super(LightningPayerDialog, self).open(*args, **kwargs)
       +    #def dismiss(self, *args, **kwargs):
       +    #    super(LightningPayerDialog, self).dismiss(*args, **kwargs)
       +
       +    def do_paste_xclip(self):
       +        import subprocess
       +        proc = subprocess.run(["xclip","-sel","clipboard","-o"], stdout=subprocess.PIPE)
       +        self.invoice_data = proc.stdout.decode("ascii")
       +
       +    def do_paste(self):
       +        contents = self.app._clipboard.paste()
       +        if not contents:
       +            self.app.show_info(_("Clipboard is empty"))
       +            return
       +        self.invoice_data = contents
       +
       +    def do_clear(self):
       +        self.invoice_data = ""
       +
       +    def do_open_channel(self):
       +        compressed_pubkey_bytes = lndecode(self.invoice_data).pubkey.serialize()
       +        hexpubkey = binascii.hexlify(compressed_pubkey_bytes).decode("ascii")
       +        local_amt = 200000
       +        push_amt = 100000
       +
       +        def on_success(pw):
       +            # node_id, local_amt, push_amt, emit_function, get_password
       +            self.app.wallet.lnworker.open_channel_from_other_thread(hexpubkey, local_amt, push_amt, mainthread(lambda parent: self.app.show_info(_("Channel open, waiting for locking..."))), lambda: pw)
       +
       +        if self.app.wallet.has_keystore_encryption():
       +            # wallet, msg, on_success (Tuple[str, str] -> ()), on_failure (() -> ())
       +            self.app.password_dialog(self.app.wallet, _("Password needed for opening channel"), on_success, lambda: self.app.show_error(_("Failed getting password from you")))
       +        else:
       +            on_success("")
       +
       +    def do_pay(self):
       +        self.app.wallet.lnworker.pay_invoice_from_other_thread(self.invoice_data)
       +
       +    def on_lightning_qr(self, data):
       +        self.invoice_data = str(data)
   DIR diff --git a/gui/qt/channels_list.py b/electrum/gui/qt/channels_list.py
   DIR diff --git a/lib/lightning.json b/electrum/lightning.json
   DIR diff --git a/lib/lnaddr.py b/electrum/lnaddr.py
   DIR diff --git a/lib/lnbase.py b/electrum/lnbase.py
   DIR diff --git a/lib/lnhtlc.py b/electrum/lnhtlc.py
   DIR diff --git a/lib/lnrouter.py b/electrum/lnrouter.py
   DIR diff --git a/lib/lnutil.py b/electrum/lnutil.py
   DIR diff --git a/lib/lnwatcher.py b/electrum/lnwatcher.py
   DIR diff --git a/lib/lnworker.py b/electrum/lnworker.py
   DIR diff --git a/electrum/tests/test_bolt11.py b/electrum/tests/test_bolt11.py
       t@@ -0,0 +1,97 @@
       +from hashlib import sha256
       +from electrum.lnaddr import shorten_amount, unshorten_amount, LnAddr, lnencode, lndecode, u5_to_bitarray, bitarray_to_u5
       +from decimal import Decimal
       +from binascii import unhexlify, hexlify
       +from electrum.segwit_addr import bech32_encode, bech32_decode
       +import pprint
       +import unittest
       +
       +RHASH=unhexlify('0001020304050607080900010203040506070809000102030405060708090102')
       +CONVERSION_RATE=1200
       +PRIVKEY=unhexlify('e126f68f7eafcc8b74f54d269fe206be715000f94dac067d1c04a8ca3b2db734')
       +PUBKEY=unhexlify('03e7156ae33b0a208d0744199163177e909e80176e55d97a2f221ede0f934dd9ad')
       +
       +class TestBolt11(unittest.TestCase):
       +    def test_shorten_amount(self):
       +        tests = {
       +            Decimal(10)/10**12: '10p',
       +            Decimal(1000)/10**12: '1n',
       +            Decimal(1200)/10**12: '1200p',
       +            Decimal(123)/10**6: '123u',
       +            Decimal(123)/1000: '123m',
       +            Decimal(3): '3',
       +        }
       +
       +        for i, o in tests.items():
       +            assert shorten_amount(i) == o
       +            assert unshorten_amount(shorten_amount(i)) == i
       +
       +    @staticmethod
       +    def compare(a, b):
       +
       +        if len([t[1] for t in a.tags if t[0] == 'h']) == 1:
       +            h1 = sha256([t[1] for t in a.tags if t[0] == 'h'][0].encode('utf-8')).digest()
       +            h2 = [t[1] for t in b.tags if t[0] == 'h'][0]
       +            assert h1 == h2
       +
       +        # Need to filter out these, since they are being modified during
       +        # encoding, i.e., hashed
       +        a.tags = [t for t in a.tags if t[0] != 'h' and t[0] != 'n']
       +        b.tags = [t for t in b.tags if t[0] != 'h' and t[0] != 'n']
       +
       +        assert b.pubkey.serialize() == PUBKEY, (hexlify(b.pubkey.serialize()), hexlify(PUBKEY))
       +        assert b.signature != None
       +
       +        # Unset these, they are generated during encoding/decoding
       +        b.pubkey = None
       +        b.signature = None
       +
       +        assert a.__dict__ == b.__dict__, (pprint.pformat([a.__dict__, b.__dict__]))
       +
       +    def test_roundtrip(self):
       +        longdescription = ('One piece of chocolate cake, one icecream cone, one'
       +                          ' pickle, one slice of swiss cheese, one slice of salami,'
       +                          ' one lollypop, one piece of cherry pie, one sausage, one'
       +                          ' cupcake, and one slice of watermelon')
       +
       +
       +        tests = [
       +            LnAddr(RHASH, tags=[('d', '')]),
       +            LnAddr(RHASH, amount=Decimal('0.001'),
       +                   tags=[('d', '1 cup coffee'), ('x', 60)]),
       +            LnAddr(RHASH, amount=Decimal('1'), tags=[('h', longdescription)]),
       +            LnAddr(RHASH, currency='tb', tags=[('f', 'mk2QpYatsKicvFVuTAQLBryyccRXMUaGHP'), ('h', longdescription)]),
       +            LnAddr(RHASH, amount=24, tags=[
       +                ('r', [(unhexlify('029e03a901b85534ff1e92c43c74431f7ce72046060fcf7a95c37e148f78c77255'), unhexlify('0102030405060708'), 1, 20, 3), (unhexlify('039e03a901b85534ff1e92c43c74431f7ce72046060fcf7a95c37e148f78c77255'), unhexlify('030405060708090a'), 2, 30, 4)]), ('f', '1RustyRX2oai4EYYDpQGWvEL62BBGqN9T'), ('h', longdescription)]),
       +            LnAddr(RHASH, amount=24, tags=[('f', '3EktnHQD7RiAE6uzMj2ZifT9YgRrkSgzQX'), ('h', longdescription)]),
       +            LnAddr(RHASH, amount=24, tags=[('f', 'bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4'), ('h', longdescription)]),
       +            LnAddr(RHASH, amount=24, tags=[('f', 'bc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qccfmv3'), ('h', longdescription)]),
       +            LnAddr(RHASH, amount=24, tags=[('n', PUBKEY), ('h', longdescription)]),
       +        ]
       +
       +        # Roundtrip
       +        for t in tests:
       +            o = lndecode(lnencode(t, PRIVKEY), False, t.currency)
       +            self.compare(t, o)
       +
       +    def test_n_decoding(self):
       +        # We flip the signature recovery bit, which would normally give a different
       +        # pubkey.
       +        hrp, data = bech32_decode(lnencode(LnAddr(RHASH, amount=24, tags=[('d', '')]), PRIVKEY), True)
       +        databits = u5_to_bitarray(data)
       +        databits.invert(-1)
       +        lnaddr = lndecode(bech32_encode(hrp, bitarray_to_u5(databits)), True)
       +        assert lnaddr.pubkey.serialize() != PUBKEY
       +
       +        # But not if we supply expliciy `n` specifier!
       +        hrp, data = bech32_decode(lnencode(LnAddr(RHASH, amount=24,
       +                                                  tags=[('d', ''),
       +                                                        ('n', PUBKEY)]),
       +                                           PRIVKEY), True)
       +        databits = u5_to_bitarray(data)
       +        databits.invert(-1)
       +        lnaddr = lndecode(bech32_encode(hrp, bitarray_to_u5(databits)), True)
       +        assert lnaddr.pubkey.serialize() == PUBKEY
       +
       +    def test_min_final_cltv_expiry(self):
       +        self.assertEquals(lndecode("lnsb500u1pdsgyf3pp5nmrqejdsdgs4n9ukgxcp2kcq265yhrxd4k5dyue58rxtp5y83s3qdqqcqzystrggccm9yvkr5yqx83jxll0qjpmgfg9ywmcd8g33msfgmqgyfyvqhku80qmqm8q6v35zvck2y5ccxsz5avtrauz8hgjj3uahppyq20qp6dvwxe", expected_hrp="sb").min_final_cltv_expiry, 144)
   DIR diff --git a/electrum/tests/test_lnhtlc.py b/electrum/tests/test_lnhtlc.py
       t@@ -0,0 +1,344 @@
       +# ported from lnd 42de4400bff5105352d0552155f73589166d162b
       +
       +import unittest
       +import electrum.bitcoin as bitcoin
       +import electrum.lnbase as lnbase
       +import electrum.lnhtlc as lnhtlc
       +import electrum.lnutil as lnutil
       +import electrum.util as util
       +import os
       +import binascii
       +
       +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()
       +    local_config=lnbase.ChannelConfig(
       +        payment_basepoint=privkeys[0],
       +        multisig_key=privkeys[1],
       +        htlc_basepoint=privkeys[2],
       +        delayed_basepoint=privkeys[3],
       +        revocation_basepoint=privkeys[4],
       +        to_self_delay=l_csv,
       +        dust_limit_sat=l_dust,
       +        max_htlc_value_in_flight_msat=500000 * 1000,
       +        max_accepted_htlcs=5
       +    )
       +    remote_config=lnbase.ChannelConfig(
       +        payment_basepoint=other_pubkeys[0],
       +        multisig_key=other_pubkeys[1],
       +        htlc_basepoint=other_pubkeys[2],
       +        delayed_basepoint=other_pubkeys[3],
       +        revocation_basepoint=other_pubkeys[4],
       +        to_self_delay=r_csv,
       +        dust_limit_sat=r_dust,
       +        max_htlc_value_in_flight_msat=500000 * 1000,
       +        max_accepted_htlcs=5
       +    )
       +
       +    return {
       +            "channel_id":channel_id,
       +            "short_channel_id":channel_id[:8],
       +            "funding_outpoint":lnbase.Outpoint(funding_txid, funding_index),
       +            "local_config":local_config,
       +            "remote_config":remote_config,
       +            "remote_state":lnbase.RemoteState(
       +                ctn = 0,
       +                next_per_commitment_point=nex,
       +                current_per_commitment_point=cur,
       +                amount_msat=remote_amount,
       +                revocation_store=their_revocation_store,
       +                next_htlc_id = 0,
       +                feerate=local_feerate
       +            ),
       +            "local_state":lnbase.LocalState(
       +                ctn = 0,
       +                per_commitment_secret_seed=seed,
       +                amount_msat=local_amount,
       +                next_htlc_id = 0,
       +                funding_locked_received=True,
       +                was_announced=False,
       +                current_commitment_signature=None,
       +                feerate=local_feerate
       +            ),
       +            "constraints":lnbase.ChannelConstraints(capacity=funding_sat, is_initiator=is_initiator, funding_txn_minimum_depth=3),
       +            "node_id":other_node_id
       +    }
       +
       +def bip32(sequence):
       +    xprv, xpub = bitcoin.bip32_root(b"9dk", 'standard')
       +    xprv, xpub = bitcoin.bip32_private_derivation(xprv, "m/", sequence)
       +    xtype, depth, fingerprint, child_number, c, k = bitcoin.deserialize_xprv(xprv)
       +    assert len(k) == 32
       +    assert type(k) is bytes
       +    return k
       +
       +def create_test_channels():
       +    funding_txid = binascii.hexlify(os.urandom(32)).decode("ascii")
       +    funding_index = 0
       +    funding_sat = bitcoin.COIN * 10
       +    local_amount = (funding_sat * 1000) // 2
       +    remote_amount = (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 = [lnbase.Keypair(lnbase.privkey_to_pubkey(x), x) for x in alice_raw]
       +    bob_privkeys = [lnbase.Keypair(lnbase.privkey_to_pubkey(x), x) for x in bob_raw]
       +    alice_pubkeys = [lnbase.OnlyPubkeyKeypair(x.pubkey) for x in alice_privkeys]
       +    bob_pubkeys = [lnbase.OnlyPubkeyKeypair(x.pubkey) for x in bob_privkeys]
       +
       +    alice_seed = os.urandom(32)
       +    bob_seed = os.urandom(32)
       +
       +    alice_cur = lnutil.secret_to_pubkey(int.from_bytes(lnutil.get_per_commitment_secret_from_seed(alice_seed, 2**48 - 1), "big"))
       +    alice_next = lnutil.secret_to_pubkey(int.from_bytes(lnutil.get_per_commitment_secret_from_seed(alice_seed, 2**48 - 2), "big"))
       +    bob_cur = lnutil.secret_to_pubkey(int.from_bytes(lnutil.get_per_commitment_secret_from_seed(bob_seed, 2**48 - 1), "big"))
       +    bob_next = lnutil.secret_to_pubkey(int.from_bytes(lnutil.get_per_commitment_secret_from_seed(bob_seed, 2**48 - 2), "big"))
       +
       +    return \
       +        lnhtlc.HTLCStateMachine(
       +            create_channel_state(funding_txid, funding_index, funding_sat, 6000, True, local_amount, remote_amount, alice_privkeys, bob_pubkeys, alice_seed, bob_cur, bob_next, b"\x02"*33, l_dust=200, r_dust=1300, l_csv=5, r_csv=4), "alice"), \
       +        lnhtlc.HTLCStateMachine(
       +            create_channel_state(funding_txid, funding_index, funding_sat, 6000, False, remote_amount, local_amount, bob_privkeys, alice_pubkeys, bob_seed, alice_cur, alice_next, b"\x01"*33, l_dust=1300, r_dust=200, l_csv=4, r_csv=5), "bob")
       +
       +one_bitcoin_in_msat = bitcoin.COIN * 1000
       +
       +class TestLNBaseHTLCStateMachine(unittest.TestCase):
       +    def assertOutputExistsByValue(self, tx, amt_sat):
       +        for typ, scr, val in tx.outputs():
       +            if val == amt_sat:
       +                break
       +        else:
       +            self.assertFalse()
       +
       +    def setUp(self):
       +        # Create a test channel which will be used for the duration of this
       +        # unittest. The channel will be funded evenly with Alice having 5 BTC,
       +        # and Bob having 5 BTC.
       +        self.alice_channel, self.bob_channel = create_test_channels()
       +
       +        self.paymentPreimage = b"\x01" * 32
       +        paymentHash = bitcoin.sha256(self.paymentPreimage)
       +        self.htlc = lnhtlc.UpdateAddHtlc(
       +            payment_hash = paymentHash,
       +            amount_msat =  one_bitcoin_in_msat,
       +            cltv_expiry =  5,
       +            total_fee = 0
       +        )
       +
       +        # First Alice adds the outgoing HTLC to her local channel's state
       +        # update log. Then Alice sends this wire message over to Bob who adds
       +        # this htlc to his remote state update log.
       +        self.aliceHtlcIndex = self.alice_channel.add_htlc(self.htlc)
       +
       +        self.bobHtlcIndex = self.bob_channel.receive_htlc(self.htlc)
       +
       +    def test_SimpleAddSettleWorkflow(self):
       +        alice_channel, bob_channel = self.alice_channel, self.bob_channel
       +        htlc = self.htlc
       +
       +        # Next alice commits this change by sending a signature message. Since
       +        # we expect the messages to be ordered, Bob will receive the HTLC we
       +        # just sent before he receives this signature, so the signature will
       +        # cover the HTLC.
       +        aliceSig, aliceHtlcSigs = alice_channel.sign_next_commitment()
       +
       +        self.assertEqual(len(aliceHtlcSigs), 1, "alice should generate one htlc signature")
       +
       +        # Bob receives this signature message, and checks that this covers the
       +        # state he has in his remote log. This includes the HTLC just sent
       +        # from Alice.
       +        bob_channel.receive_new_commitment(aliceSig, aliceHtlcSigs)
       +
       +        # Bob revokes his prior commitment given to him by Alice, since he now
       +        # has a valid signature for a newer commitment.
       +        bobRevocation, _ = bob_channel.revoke_current_commitment()
       +
       +        # Bob finally send a signature for Alice's commitment transaction.
       +        # This signature will cover the HTLC, since Bob will first send the
       +        # revocation just created. The revocation also acks every received
       +        # HTLC up to the point where Alice sent here signature.
       +        bobSig, bobHtlcSigs = bob_channel.sign_next_commitment()
       +
       +        # Alice then processes this revocation, sending her own revocation for
       +        # her prior commitment transaction. Alice shouldn't have any HTLCs to
       +        # forward since she's sending an outgoing HTLC.
       +        alice_channel.receive_revocation(bobRevocation)
       +
       +        # Alice then processes bob's signature, and since she just received
       +        # the revocation, she expect this signature to cover everything up to
       +        # the point where she sent her signature, including the HTLC.
       +        alice_channel.receive_new_commitment(bobSig, bobHtlcSigs)
       +
       +        # Alice then generates a revocation for bob.
       +        aliceRevocation, _ = alice_channel.revoke_current_commitment()
       +
       +        # Finally Bob processes Alice's revocation, at this point the new HTLC
       +        # is fully locked in within both commitment transactions. Bob should
       +        # also be able to forward an HTLC now that the HTLC has been locked
       +        # into both commitment transactions.
       +        bob_channel.receive_revocation(aliceRevocation)
       +
       +        # At this point, both sides should have the proper number of satoshis
       +        # sent, and commitment height updated within their local channel
       +        # state.
       +        aliceSent = 0
       +        bobSent = 0
       +
       +        self.assertEqual(alice_channel.total_msat_sent, aliceSent, "alice has incorrect milli-satoshis sent")
       +        self.assertEqual(alice_channel.total_msat_received, bobSent, "alice has incorrect milli-satoshis received")
       +        self.assertEqual(bob_channel.total_msat_sent, bobSent, "bob has incorrect milli-satoshis sent")
       +        self.assertEqual(bob_channel.total_msat_received, aliceSent, "bob has incorrect milli-satoshis received")
       +        self.assertEqual(bob_channel.local_state.ctn, 1, "bob has incorrect commitment height")
       +        self.assertEqual(alice_channel.local_state.ctn, 1, "alice has incorrect commitment height")
       +
       +        # Both commitment transactions should have three outputs, and one of
       +        # them should be exactly the amount of the HTLC.
       +        self.assertEqual(len(alice_channel.local_commitment.outputs()), 3, "alice should have three commitment outputs, instead have %s"% len(alice_channel.local_commitment.outputs()))
       +        self.assertEqual(len(bob_channel.local_commitment.outputs()), 3, "bob should have three commitment outputs, instead have %s"% len(bob_channel.local_commitment.outputs()))
       +        self.assertOutputExistsByValue(alice_channel.local_commitment, htlc.amount_msat // 1000)
       +        self.assertOutputExistsByValue(bob_channel.local_commitment, htlc.amount_msat // 1000)
       +
       +        # Now we'll repeat a similar exchange, this time with Bob settling the
       +        # HTLC once he learns of the preimage.
       +        preimage = self.paymentPreimage
       +        bob_channel.settle_htlc(preimage, self.bobHtlcIndex)
       +
       +        alice_channel.receive_htlc_settle(preimage, self.aliceHtlcIndex)
       +
       +        bobSig2, bobHtlcSigs2 = bob_channel.sign_next_commitment()
       +        alice_channel.receive_new_commitment(bobSig2, bobHtlcSigs2)
       +
       +        aliceRevocation2, _ = alice_channel.revoke_current_commitment()
       +        aliceSig2, aliceHtlcSigs2 = alice_channel.sign_next_commitment()
       +        self.assertEqual(aliceHtlcSigs2, [], "alice should generate no htlc signatures")
       +
       +        bob_channel.receive_revocation(aliceRevocation2)
       +
       +        bob_channel.receive_new_commitment(aliceSig2, aliceHtlcSigs2)
       +
       +        bobRevocation2, _ = bob_channel.revoke_current_commitment()
       +        alice_channel.receive_revocation(bobRevocation2)
       +
       +        # At this point, Bob should have 6 BTC settled, with Alice still having
       +        # 4 BTC. Alice's channel should show 1 BTC sent and Bob's channel
       +        # should show 1 BTC received. They should also be at commitment height
       +        # two, with the revocation window extended by 1 (5).
       +        mSatTransferred = one_bitcoin_in_msat
       +        self.assertEqual(alice_channel.total_msat_sent, mSatTransferred, "alice satoshis sent incorrect %s vs %s expected"% (alice_channel.total_msat_sent, mSatTransferred))
       +        self.assertEqual(alice_channel.total_msat_received, 0, "alice satoshis received incorrect %s vs %s expected"% (alice_channel.total_msat_received, 0))
       +        self.assertEqual(bob_channel.total_msat_received, mSatTransferred, "bob satoshis received incorrect %s vs %s expected"% (bob_channel.total_msat_received, mSatTransferred))
       +        self.assertEqual(bob_channel.total_msat_sent, 0, "bob satoshis sent incorrect %s vs %s expected"% (bob_channel.total_msat_sent, 0))
       +        self.assertEqual(bob_channel.l_current_height, 2, "bob has incorrect commitment height, %s vs %s"% (bob_channel.l_current_height, 2))
       +        self.assertEqual(alice_channel.l_current_height, 2, "alice has incorrect commitment height, %s vs %s"% (alice_channel.l_current_height, 2))
       +
       +        # The logs of both sides should now be cleared since the entry adding
       +        # the HTLC should have been removed once both sides receive the
       +        # revocation.
       +        self.assertEqual(alice_channel.local_update_log, [], "alice's local not updated, should be empty, has %s entries instead"% len(alice_channel.local_update_log))
       +        self.assertEqual(alice_channel.remote_update_log, [], "alice's remote not updated, should be empty, has %s entries instead"% len(alice_channel.remote_update_log))
       +
       +    def alice_to_bob_fee_update(self):
       +        fee = 111
       +        self.alice_channel.update_fee(fee)
       +        self.bob_channel.receive_update_fee(fee)
       +        return fee
       +
       +    def test_UpdateFeeSenderCommits(self):
       +        fee = self.alice_to_bob_fee_update()
       +
       +        alice_channel, bob_channel = self.alice_channel, self.bob_channel
       +
       +        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.local_state.feerate)
       +        rev, _ = bob_channel.revoke_current_commitment()
       +        self.assertEqual(fee, bob_channel.local_state.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.local_state.feerate)
       +        rev, _ = alice_channel.revoke_current_commitment()
       +        self.assertEqual(fee, alice_channel.local_state.feerate)
       +
       +        bob_channel.receive_revocation(rev)
       +
       +
       +    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.local_state.feerate)
       +        bob_revocation, _ = bob_channel.revoke_current_commitment()
       +        self.assertEqual(fee, bob_channel.local_state.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.local_state.feerate)
       +        alice_revocation, _ = alice_channel.revoke_current_commitment()
       +        self.assertEqual(fee, alice_channel.local_state.feerate)
       +
       +        bob_channel.receive_revocation(alice_revocation)
       +
       +
       +
       +class TestLNHTLCDust(unittest.TestCase):
       +    def test_HTLCDustLimit(self):
       +        alice_channel, bob_channel = create_test_channels()
       +
       +        paymentPreimage = b"\x01" * 32
       +        paymentHash = bitcoin.sha256(paymentPreimage)
       +        fee_per_kw = alice_channel.local_state.feerate
       +        self.assertEqual(fee_per_kw, 6000)
       +        htlcAmt = 500 + lnutil.HTLC_TIMEOUT_WEIGHT * (fee_per_kw // 1000)
       +        self.assertEqual(htlcAmt, 4478)
       +        htlc = lnhtlc.UpdateAddHtlc(
       +            payment_hash = paymentHash,
       +            amount_msat =  1000 * htlcAmt,
       +            cltv_expiry =  5, # also in create_test_channels
       +            total_fee = 0
       +        )
       +
       +        aliceHtlcIndex = alice_channel.add_htlc(htlc)
       +        bobHtlcIndex = bob_channel.receive_htlc(htlc)
       +        force_state_transition(alice_channel, bob_channel)
       +        self.assertEqual(len(alice_channel.local_commitment.outputs()), 3)
       +        self.assertEqual(len(bob_channel.local_commitment.outputs()), 2)
       +        default_fee = calc_static_fee(0)
       +        self.assertEqual(bob_channel.local_commit_fee, default_fee + htlcAmt)
       +        bob_channel.settle_htlc(paymentPreimage, htlc.htlc_id)
       +        alice_channel.receive_htlc_settle(paymentPreimage, aliceHtlcIndex)
       +        force_state_transition(bob_channel, alice_channel)
       +        self.assertEqual(len(alice_channel.local_commitment.outputs()), 2)
       +        self.assertEqual(alice_channel.total_msat_sent // 1000, htlcAmt)
       +
       +def force_state_transition(chanA, chanB):
       +    chanB.receive_new_commitment(*chanA.sign_next_commitment())
       +    rev, _ = chanB.revoke_current_commitment()
       +    bob_sig, bob_htlc_sigs = chanB.sign_next_commitment()
       +    chanA.receive_revocation(rev)
       +    chanA.receive_new_commitment(bob_sig, bob_htlc_sigs)
       +    chanB.receive_revocation(chanA.revoke_current_commitment()[0])
       +
       +# calcStaticFee calculates appropriate fees for commitment transactions.  This
       +# function provides a simple way to allow test balance assertions to take fee
       +# calculations into account.
       +def calc_static_fee(numHTLCs):
       +    commitWeight = 724
       +    htlcWeight   = 172
       +    feePerKw     = 24//4 * 1000
       +    return feePerKw * (commitWeight + htlcWeight*numHTLCs) // 1000
   DIR diff --git a/electrum/tests/test_lnrouter.py b/electrum/tests/test_lnrouter.py
       t@@ -0,0 +1,151 @@
       +import unittest
       +
       +from electrum.util import bh2u, bfh
       +from electrum.lnbase import Peer
       +from electrum.lnrouter import OnionHopsDataSingle, new_onion_packet, OnionPerHop
       +from electrum import bitcoin, lnrouter
       +
       +class Test_LNRouter(unittest.TestCase):
       +
       +    #@staticmethod
       +    #def parse_witness_list(witness_bytes):
       +    #    amount_witnesses = witness_bytes[0]
       +    #    witness_bytes = witness_bytes[1:]
       +    #    res = []
       +    #    for i in range(amount_witnesses):
       +    #        witness_length = witness_bytes[0]
       +    #        this_witness = witness_bytes[1:witness_length+1]
       +    #        assert len(this_witness) == witness_length
       +    #        witness_bytes = witness_bytes[witness_length+1:]
       +    #        res += [bytes(this_witness)]
       +    #    assert witness_bytes == b"", witness_bytes
       +    #    return res
       +
       +
       +
       +    def test_find_path_for_payment(self):
       +        class fake_network:
       +            channel_db = lnrouter.ChannelDB()
       +            trigger_callback = lambda x: None
       +        class fake_ln_worker:
       +            path_finder = lnrouter.LNPathFinder(fake_network.channel_db)
       +            privkey = bitcoin.sha256('privkeyseed')
       +            network = fake_network
       +            channel_state = {}
       +            channels = []
       +            invoices = {}
       +        p = Peer(fake_ln_worker, '', 0, 'a')
       +        p.on_channel_announcement({'node_id_1': b'b', 'node_id_2': b'c', 'short_channel_id': bfh('0000000000000001')})
       +        p.on_channel_announcement({'node_id_1': b'b', 'node_id_2': b'e', 'short_channel_id': bfh('0000000000000002')})
       +        p.on_channel_announcement({'node_id_1': b'a', 'node_id_2': b'b', 'short_channel_id': bfh('0000000000000003')})
       +        p.on_channel_announcement({'node_id_1': b'c', 'node_id_2': b'd', 'short_channel_id': bfh('0000000000000004')})
       +        p.on_channel_announcement({'node_id_1': b'd', 'node_id_2': b'e', 'short_channel_id': bfh('0000000000000005')})
       +        p.on_channel_announcement({'node_id_1': b'a', 'node_id_2': b'd', 'short_channel_id': bfh('0000000000000006')})
       +        o = lambda i: i.to_bytes(8, "big")
       +        p.on_channel_update({'short_channel_id': bfh('0000000000000001'), 'flags': b'\x00', 'cltv_expiry_delta': o(10), 'htlc_minimum_msat': o(250), 'fee_base_msat': o(100), 'fee_proportional_millionths': o(150)})
       +        p.on_channel_update({'short_channel_id': bfh('0000000000000001'), 'flags': b'\x01', 'cltv_expiry_delta': o(10), 'htlc_minimum_msat': o(250), 'fee_base_msat': o(100), 'fee_proportional_millionths': o(150)})
       +        p.on_channel_update({'short_channel_id': bfh('0000000000000002'), 'flags': b'\x00', 'cltv_expiry_delta': o(99), 'htlc_minimum_msat': o(250), 'fee_base_msat': o(100), 'fee_proportional_millionths': o(150)})
       +        p.on_channel_update({'short_channel_id': bfh('0000000000000002'), 'flags': b'\x01', 'cltv_expiry_delta': o(10), 'htlc_minimum_msat': o(250), 'fee_base_msat': o(100), 'fee_proportional_millionths': o(150)})
       +        p.on_channel_update({'short_channel_id': bfh('0000000000000003'), 'flags': b'\x01', 'cltv_expiry_delta': o(10), 'htlc_minimum_msat': o(250), 'fee_base_msat': o(100), 'fee_proportional_millionths': o(150)})
       +        p.on_channel_update({'short_channel_id': bfh('0000000000000003'), 'flags': b'\x00', 'cltv_expiry_delta': o(10), 'htlc_minimum_msat': o(250), 'fee_base_msat': o(100), 'fee_proportional_millionths': o(150)})
       +        p.on_channel_update({'short_channel_id': bfh('0000000000000004'), 'flags': b'\x01', 'cltv_expiry_delta': o(10), 'htlc_minimum_msat': o(250), 'fee_base_msat': o(100), 'fee_proportional_millionths': o(150)})
       +        p.on_channel_update({'short_channel_id': bfh('0000000000000004'), 'flags': b'\x00', 'cltv_expiry_delta': o(10), 'htlc_minimum_msat': o(250), 'fee_base_msat': o(100), 'fee_proportional_millionths': o(150)})
       +        p.on_channel_update({'short_channel_id': bfh('0000000000000005'), 'flags': b'\x01', 'cltv_expiry_delta': o(10), 'htlc_minimum_msat': o(250), 'fee_base_msat': o(100), 'fee_proportional_millionths': o(150)})
       +        p.on_channel_update({'short_channel_id': bfh('0000000000000005'), 'flags': b'\x00', 'cltv_expiry_delta': o(10), 'htlc_minimum_msat': o(250), 'fee_base_msat': o(100), 'fee_proportional_millionths': o(999)})
       +        p.on_channel_update({'short_channel_id': bfh('0000000000000006'), 'flags': b'\x00', 'cltv_expiry_delta': o(10), 'htlc_minimum_msat': o(250), 'fee_base_msat': o(100), 'fee_proportional_millionths': o(99999999)})
       +        p.on_channel_update({'short_channel_id': bfh('0000000000000006'), 'flags': b'\x01', 'cltv_expiry_delta': o(10), 'htlc_minimum_msat': o(250), 'fee_base_msat': o(100), 'fee_proportional_millionths': o(150)})
       +        self.assertNotEqual(None, fake_ln_worker.path_finder.find_path_for_payment(b'a', b'e', 100000))
       +
       +
       +
       +    def test_new_onion_packet(self):
       +        # test vector from bolt-04
       +        payment_path_pubkeys = [
       +            bfh('02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619'),
       +            bfh('0324653eac434488002cc06bbfb7f10fe18991e35f9fe4302dbea6d2353dc0ab1c'),
       +            bfh('027f31ebc5462c1fdce1b737ecff52d37d75dea43ce11c74d25aa297165faa2007'),
       +            bfh('032c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991'),
       +            bfh('02edabbd16b41c8371b92ef2f04c1185b4f03b6dcd52ba9b78d9d7c89c8f221145'),
       +        ]
       +        session_key = bfh('4141414141414141414141414141414141414141414141414141414141414141')
       +        associated_data = bfh('4242424242424242424242424242424242424242424242424242424242424242')
       +        hops_data = [
       +            OnionHopsDataSingle(OnionPerHop(
       +                bfh('0000000000000000'), bfh('0000000000000000'), bfh('00000000')
       +            )),
       +            OnionHopsDataSingle(OnionPerHop(
       +                bfh('0101010101010101'), bfh('0000000000000001'), bfh('00000001')
       +            )),
       +            OnionHopsDataSingle(OnionPerHop(
       +                bfh('0202020202020202'), bfh('0000000000000002'), bfh('00000002')
       +            )),
       +            OnionHopsDataSingle(OnionPerHop(
       +                bfh('0303030303030303'), bfh('0000000000000003'), bfh('00000003')
       +            )),
       +            OnionHopsDataSingle(OnionPerHop(
       +                bfh('0404040404040404'), bfh('0000000000000004'), bfh('00000004')
       +            )),
       +        ]
       +        packet = new_onion_packet(payment_path_pubkeys, session_key, hops_data, associated_data)
parazyd.org:70 /git/electrum/commit/35adc3231b297d03ad0a0534d65665ad14c0d9f6.gph:805: line too long