From 76886d0eaca874a476f893894da2e9234c6c5074 Mon Sep 17 00:00:00 2001 From: Janus Date: Mon, 12 Nov 2018 18:01:59 +0100 Subject: [PATCH] Kivy: open channel dialog --- .gitignore | 1 + electrum/gui/kivy/Makefile | 1 + electrum/gui/kivy/main.kv | 5 +- electrum/gui/kivy/main_window.py | 12 +- electrum/gui/kivy/theming/light/network.png | Bin 2412 -> 0 bytes electrum/gui/kivy/theming/light/network.svg | 6 + .../kivy/uix/dialogs/lightning_channels.py | 54 ++++--- .../uix/dialogs/lightning_open_channel.py | 141 ++++++++++++++++++ .../gui/kivy/uix/dialogs/lightning_payer.py | 93 ------------ electrum/gui/kivy/uix/screens.py | 8 +- electrum/lnworker.py | 3 + 11 files changed, 203 insertions(+), 121 deletions(-) delete mode 100644 electrum/gui/kivy/theming/light/network.png create mode 100644 electrum/gui/kivy/theming/light/network.svg create mode 100644 electrum/gui/kivy/uix/dialogs/lightning_open_channel.py delete mode 100644 electrum/gui/kivy/uix/dialogs/lightning_payer.py diff --git a/.gitignore b/.gitignore index 84d5565c9..214deb25e 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,7 @@ bin/ # icons electrum/gui/kivy/theming/light-0.png electrum/gui/kivy/theming/light.atlas +electrum/gui/kivy/theming/light/network.png # tests/tox .tox/ diff --git a/electrum/gui/kivy/Makefile b/electrum/gui/kivy/Makefile index 868a774a0..44667efd1 100644 --- a/electrum/gui/kivy/Makefile +++ b/electrum/gui/kivy/Makefile @@ -5,6 +5,7 @@ PYTHON = python3 .PHONY: theming apk clean theming: + bash -c "convert -background none theming/light/network.{svg,png}" $(PYTHON) -m kivy.atlas theming/light 1024 theming/light/*.png prepare: # running pre build setup diff --git a/electrum/gui/kivy/main.kv b/electrum/gui/kivy/main.kv index a4e2758f3..a9fc221c3 100644 --- a/electrum/gui/kivy/main.kv +++ b/electrum/gui/kivy/main.kv @@ -449,12 +449,9 @@ BoxLayout: ActionOvrButton: name: 'network' text: _('Network') - ActionOvrButton: - name: 'lightning_payer_dialog' - text: _('Pay Lightning Invoice') ActionOvrButton: name: 'lightning_channels_dialog' - text: _('Lightning Channels') + text: _('Channels') ActionOvrButton: name: 'settings' text: _('Settings') diff --git a/electrum/gui/kivy/main_window.py b/electrum/gui/kivy/main_window.py index dd8cd5b11..d59eefecd 100644 --- a/electrum/gui/kivy/main_window.py +++ b/electrum/gui/kivy/main_window.py @@ -75,7 +75,7 @@ from electrum.util import (base_units, NoDynamicFeeEstimates, decimal_point_to_b base_unit_name_to_decimal_point, NotEnoughFunds, UnknownBaseUnit, DECIMAL_POINT_DEFAULT) -from .uix.dialogs.lightning_payer import LightningPayerDialog +from .uix.dialogs.lightning_open_channel import LightningOpenChannelDialog from .uix.dialogs.lightning_channels import LightningChannelsDialog class ElectrumWindow(App): @@ -645,8 +645,8 @@ class ElectrumWindow(App): self._settings_dialog.update() self._settings_dialog.open() - def lightning_payer_dialog(self): - d = LightningPayerDialog(self) + def lightning_open_channel_dialog(self): + d = LightningOpenChannelDialog(self) d.open() def lightning_channels_dialog(self): @@ -803,7 +803,11 @@ class ElectrumWindow(App): inputs = self.wallet.get_spendable_coins(None, self.electrum_config) if not inputs: return '' - addr = str(self.send_screen.screen.address) or self.wallet.dummy_address() + addr = None + if self.send_screen: + addr = str(self.send_screen.screen.address) + if not addr: + addr = self.wallet.dummy_address() outputs = [TxOutput(TYPE_ADDRESS, addr, '!')] try: tx = self.wallet.make_unsigned_transaction(inputs, outputs, self.electrum_config) diff --git a/electrum/gui/kivy/theming/light/network.png b/electrum/gui/kivy/theming/light/network.png deleted file mode 100644 index f258170118a952a6875922b0e5de8f51694628bf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2412 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 e|9|%QjO~9d&q2E_AfF`w0000 + + + + + diff --git a/electrum/gui/kivy/uix/dialogs/lightning_channels.py b/electrum/gui/kivy/uix/dialogs/lightning_channels.py index d075c44c9..ebf575cc5 100644 --- a/electrum/gui/kivy/uix/dialogs/lightning_channels.py +++ b/electrum/gui/kivy/uix/dialogs/lightning_channels.py @@ -7,22 +7,35 @@ from kivy.clock import Clock from electrum.gui.kivy.uix.context_menu import ContextMenu from electrum.util import bh2u from electrum.lnutil import LOCAL, REMOTE +from electrum.gui.kivy.i18n import _ -Builder.load_string(''' +Builder.load_string(r''' details: {} active: False channelId: '' + id: card + _chan: None Label: + color: (.5,.5,.5,1) if not card.active else (1,1,1,1) text: root.channelId + Label: + text: _('State:\n') + (card._chan.get_state() if card._chan else 'n/a') + font_size: '10sp' : name: 'lightning_channels' - title: 'Lightning channels. Tap to select.' + title: _('Lightning channels. Tap for options.') + id: popup BoxLayout: id: box orientation: 'vertical' spacing: '1dp' + Button: + size_hint: 1, None + height: '48dp' + text: _('New channel...') + on_press: popup.app.popup_dialog('lightning_open_channel_dialog') ScrollView: GridLayout: cols: 1 @@ -95,7 +108,7 @@ class LightningChannelsDialog(Factory.Popup): def show_channel_details(self, obj): p = Factory.ChannelDetailsPopup() - p.title = 'Lightning channels details for ' + self.presentable_chan_id(obj._chan) + p.title = _('Details for channel ') + self.presentable_chan_id(obj._chan) p.data = [{'keyName': key, 'value': str(obj.details[key])} for key in obj.details.keys()] p.open() @@ -104,25 +117,28 @@ class LightningChannelsDialog(Factory.Popup): coro = asyncio.run_coroutine_threadsafe(self.app.wallet.lnworker.close_channel(obj._chan.channel_id), loop) try: coro.result(5) - self.app.show_info('Channel closed') + self.app.show_info(_('Channel closed')) except Exception as e: - self.app.show_info('Could not close channel: ' + repr(e)) # repr because str(Exception()) == '' + self.app.show_info(_('Could not close channel: ') + repr(e)) # repr because str(Exception()) == '' def force_close_channel(self, obj): + if obj._chan.get_state() == 'CLOSED': + self.app.show_error(_('Channel already closed')) + return loop = self.app.wallet.network.asyncio_loop coro = asyncio.run_coroutine_threadsafe(self.app.wallet.lnworker.force_close_channel(obj._chan.channel_id), loop) try: coro.result(1) - self.app.show_info('Channel closed, you may need to wait at least ' + str(obj._chan.config[REMOTE].to_self_delay) + ' blocks, because of CSV delays') + self.app.show_info(_('Channel closed, you may need to wait at least {} blocks, because of CSV delays'.format(obj._chan.config[REMOTE].to_self_delay))) except Exception as e: - self.app.show_info('Could not force close channel: ' + repr(e)) # repr because str(Exception()) == '' + self.app.show_info(_('Could not force close channel: ') + repr(e)) # repr because str(Exception()) == '' def show_menu(self, obj): self.hide_menu() self.context_menu = ContextMenu(obj, [ - ("Force close", self.force_close_channel), - ("Co-op close", self.close_channel), - ("Details", self.show_channel_details)]) + (_("Force close"), self.force_close_channel), + (_("Co-op close"), self.close_channel), + (_("Details"), self.show_channel_details)]) self.ids.box.add_widget(self.context_menu) def hide_menu(self): @@ -136,6 +152,8 @@ class LightningChannelsDialog(Factory.Popup): def channels_update(self, evt): channel_cards = self.ids.lightning_channels_container channel_cards.clear_widgets() + if not self.app.wallet: + return lnworker = self.app.wallet.lnworker for i in lnworker.channels.values(): item = Factory.LightningChannelItem() @@ -147,10 +165,12 @@ class LightningChannelsDialog(Factory.Popup): channel_cards.add_widget(item) def channel_details(self, chan): - return {'Node ID': bh2u(chan.node_id), - 'Channel ID': bh2u(chan.channel_id), - 'Capacity': self.app.format_amount_and_units(chan.constraints.capacity), - 'Funding TXID': chan.funding_outpoint.txid, - 'Short Chan ID': bh2u(chan.short_channel_id) if chan.short_channel_id else 'Not available', - 'Available to spend': self.app.format_amount_and_units(chan.available_to_spend(LOCAL) // 1000), - 'State': chan.get_state()} + return {_('Node ID'): bh2u(chan.node_id), + _('Channel ID'): bh2u(chan.channel_id), + _('Capacity'): self.app.format_amount_and_units(chan.constraints.capacity), + _('Funding TXID'): chan.funding_outpoint.txid, + _('Short Chan ID'): bh2u(chan.short_channel_id) if chan.short_channel_id else _('Not available'), + _('Available to spend'): self.app.format_amount_and_units(chan.available_to_spend(LOCAL) // 1000), + _('State'): chan.get_state(), + _('Initiator'): 'Opened/funded by us' if chan.constraints.is_initiator else 'Opened/funded by remote party', + _('Current feerate'): chan.constraints.feerate} diff --git a/electrum/gui/kivy/uix/dialogs/lightning_open_channel.py b/electrum/gui/kivy/uix/dialogs/lightning_open_channel.py new file mode 100644 index 000000000..47c17774b --- /dev/null +++ b/electrum/gui/kivy/uix/dialogs/lightning_open_channel.py @@ -0,0 +1,141 @@ +from kivy.lang import Builder +from kivy.factory import Factory +from electrum.gui.kivy.i18n import _ +from electrum.lnaddr import lndecode +from electrum.gui.kivy.uix.dialogs.choice_dialog import ChoiceDialog +from electrum.util import bh2u +from electrum.bitcoin import COIN +import electrum.simple_config as config +from .label_dialog import LabelDialog + +Builder.load_string(''' + + id: s + name: 'lightning_open_channel' + title: _('Open Lightning Channel') + pubkey: '' + amount: '' + ipport: '' + BoxLayout + spacing: '12dp' + padding: '12dp' + orientation: 'vertical' + SendReceiveBlueBottom: + id: blue_bottom + size_hint: 1, None + height: self.minimum_height + BoxLayout: + size_hint: 1, None + height: blue_bottom.item_height + Image: + source: 'atlas://electrum/gui/kivy/theming/light/globe' + size_hint: None, None + size: '22dp', '22dp' + pos_hint: {'center_y': .5} + BlueButton: + text: s.pubkey if s.pubkey else _('Node ID, [pubkey]@[host]:[port]') + shorten: True + on_release: s.choose_node() + IconButton: + on_release: app.scan_qr(on_complete=s.on_pubkey) + icon: 'atlas://electrum/gui/kivy/theming/light/camera' + color: blue_bottom.foreground_color + size: '22dp', '22dp' + pos_hint: {'center_y': .5} + size_hint: None, None + CardSeparator: + color: blue_bottom.foreground_color + BoxLayout: + size_hint: 1, None + height: blue_bottom.item_height + Image: + source: 'atlas://electrum/gui/kivy/theming/light/network' + size_hint: None, None + size: '22dp', '22dp' + pos_hint: {'center_y': .5} + BlueButton: + text: s.ipport if s.ipport else _('Auto-detect IP/port') + on_release: s.ipport_dialog() + CardSeparator: + color: blue_bottom.foreground_color + BoxLayout: + size_hint: 1, None + height: blue_bottom.item_height + Image: + source: 'atlas://electrum/gui/kivy/theming/light/calculator' + size_hint: None, None + size: '22dp', '22dp' + pos_hint: {'center_y': .5} + BlueButton: + text: s.amount if s.amount else _('Channel capacity amount') + on_release: app.amount_dialog(s, True) + Button: + size_hint: 1, None + height: blue_bottom.item_height + text: _('Paste') + on_release: s.do_paste() + Button: + size_hint: 1, None + height: blue_bottom.item_height + text: _('Open Channel') + on_release: s.do_open_channel() +''') + +class LightningOpenChannelDialog(Factory.Popup): + def ipport_dialog(self): + def callback(text): + self.ipport = text + d = LabelDialog(_('IP/port in format:\n[host]:[port]'), self.ipport, callback) + d.open() + + def on_pubkey(self, data): + self.pubkey = data.replace('\n', '') # strip newlines if we choose from ChoiseDialog + + def choose_node(self): + lines = [] + suggested = self.app.wallet.lnworker.suggest_peer() + if suggested: + assert len(suggested) == 33 + for i in range(0, 34, 11): + lines += [bh2u(suggested[i:i+11])] + servers = ['\n'.join(lines)] + ChoiceDialog(_('Choose node to connect to'), sorted(servers), self.pubkey, self.on_pubkey).open() + + def __init__(self, app, lnaddr=None, msg=None): + super(LightningOpenChannelDialog, self).__init__() + self.app = app + self.lnaddr = lnaddr + self.msg = msg + + def open(self, *args, **kwargs): + super(LightningOpenChannelDialog, self).open(*args, **kwargs) + if self.lnaddr: + fee = self.app.electrum_config.fee_per_kb() + if not fee: + fee = config.FEERATE_FALLBACK_STATIC_FEE + self.amount = self.app.format_amount_and_units(self.lnaddr.amount * COIN + fee * 2) + self.pubkey = bh2u(self.lnaddr.pubkey.serialize()) + if self.msg: + self.app.show_info(self.msg) + + def do_paste(self): + contents = self.app._clipboard.paste() + if not contents: + self.app.show_info(_("Clipboard is empty")) + return + self.pubkey = contents + + def do_open_channel(self): + if not self.pubkey or not self.amount: + self.app.show_info(_('All fields must be filled out')) + return + conn_str = self.pubkey + if self.ipport: + conn_str += '@' + self.ipport.strip() + try: + node_id_hex = self.app.wallet.lnworker.open_channel(conn_str, self.app.get_amount(self.amount), 0) + except Exception as e: + self.app.show_error(_('Problem opening channel: ') + '\n' + repr(e)) + return + self.app.show_info(_('Please wait for confirmation, channel is opening with node ') + node_id_hex[:16]) + self.dismiss() diff --git a/electrum/gui/kivy/uix/dialogs/lightning_payer.py b/electrum/gui/kivy/uix/dialogs/lightning_payer.py deleted file mode 100644 index 83a0b6e6e..000000000 --- a/electrum/gui/kivy/uix/dialogs/lightning_payer.py +++ /dev/null @@ -1,93 +0,0 @@ -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(''' - - 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) diff --git a/electrum/gui/kivy/uix/screens.py b/electrum/gui/kivy/uix/screens.py index 02d2da380..c038a59ca 100644 --- a/electrum/gui/kivy/uix/screens.py +++ b/electrum/gui/kivy/uix/screens.py @@ -32,7 +32,7 @@ from electrum.lnaddr import lndecode from electrum.lnutil import RECEIVED, SENT from .context_menu import ContextMenu - +from .dialogs.lightning_open_channel import LightningOpenChannelDialog from electrum.gui.kivy.i18n import _ @@ -280,11 +280,13 @@ class SendScreen(CScreen): return invoice = self.screen.address amount_sat = self.app.get_amount(self.screen.amount) + addr = self.app.wallet.lnworker._check_invoice(invoice, amount_sat) try: - addr = self.app.wallet.lnworker._check_invoice(invoice, amount_sat) route = self.app.wallet.lnworker._create_route_from_invoice(decoded_invoice=addr) except Exception as e: - self.app.show_error(_('Could not find path for payment. Check if you have open channels. Error details:') + ':\n' + repr(e)) + dia = LightningOpenChannelDialog(self.app, addr, str(e) + _(':\nYou can open a channel.')) + dia.open() + return self.app.network.register_callback(self.payment_completed_async_thread, ['ln_payment_completed']) _addr, _peer, coro = self.app.wallet.lnworker._pay(invoice, amount_sat) fut = asyncio.run_coroutine_threadsafe(coro, self.app.network.asyncio_loop) diff --git a/electrum/lnworker.py b/electrum/lnworker.py index 2526bdb94..36d17be66 100644 --- a/electrum/lnworker.py +++ b/electrum/lnworker.py @@ -240,6 +240,7 @@ class LNWorker(PrintError): if conf >= chan.constraints.funding_txn_minimum_depth > 0: chan.short_channel_id = chan.short_channel_id_predicted self.save_channel(chan) + self.on_channels_updated() return True, conf return False, conf @@ -255,6 +256,7 @@ class LNWorker(PrintError): if is_spent: if chan.get_state() != 'FORCE_CLOSING': chan.set_state("CLOSED") + self.on_channels_updated() self.channel_db.remove_channel(chan.short_channel_id) self.network.trigger_callback('channel', chan) @@ -543,6 +545,7 @@ class LNWorker(PrintError): tx = chan.force_close_tx() chan.set_state('FORCE_CLOSING') self.save_channel(chan) + self.on_channels_updated() return await self.network.broadcast_transaction(tx) def _get_next_peers_to_try(self) -> Sequence[LNPeerAddr]: