diff --git a/electrum/gui/kivy/main_window.py b/electrum/gui/kivy/main_window.py index b3f60521c..1e27a2c25 100644 --- a/electrum/gui/kivy/main_window.py +++ b/electrum/gui/kivy/main_window.py @@ -24,6 +24,8 @@ from electrum import blockchain from electrum.network import Network, TxBroadcastError, BestEffortRequestFailed from electrum.interface import PREFERRED_NETWORK_PROTOCOL, ServerAddr from electrum.logging import Logger + +from electrum.gui import messages from .i18n import _ from . import KIVY_GUI_PATH @@ -727,14 +729,10 @@ class ElectrumWindow(App, Logger): if not self.wallet.has_lightning(): self.show_error(_('Lightning is not enabled for this wallet')) return - if not self.wallet.lnworker.channels: - warning1 = _("Lightning support in Electrum is experimental. " - "Do not put large amounts in lightning channels.") - warning2 = _("Funds stored in lightning channels are not recoverable " - "from your seed. You must backup your wallet file everytime " - "you create a new channel.") + if not self.wallet.lnworker.channels and not self.wallet.lnworker.channel_backups: + warning = _(messages.MSG_LIGHTNING_WARNING) d = Question(_('Do you want to create your first channel?') + - '\n\n' + warning1 + '\n\n' + warning2, self.open_channel_dialog_with_warning) + '\n\n' + warning, self.open_channel_dialog_with_warning) d.open() else: d = LightningOpenChannelDialog(self) @@ -1295,7 +1293,7 @@ class ElectrumWindow(App, Logger): def save_backup(self): if platform != 'android': - backup_dir = util.get_backup_dir(self.electrum_config) + backup_dir = self.electrum_config.get_backup_dir() if backup_dir: self._save_backup(backup_dir) else: diff --git a/electrum/gui/kivy/uix/dialogs/lightning_open_channel.py b/electrum/gui/kivy/uix/dialogs/lightning_open_channel.py index c927ee242..67d610aab 100644 --- a/electrum/gui/kivy/uix/dialogs/lightning_open_channel.py +++ b/electrum/gui/kivy/uix/dialogs/lightning_open_channel.py @@ -3,6 +3,7 @@ from typing import TYPE_CHECKING from kivy.lang import Builder from kivy.factory import Factory +from electrum.gui import messages from electrum.gui.kivy.i18n import _ from electrum.lnaddr import lndecode from electrum.util import bh2u @@ -214,11 +215,7 @@ class LightningOpenChannelDialog(Factory.Popup, Logger): self.maybe_show_funding_tx(chan, funding_tx) else: title = _('Save backup') - help_text = ' '.join([ - _('Your wallet does not have recoverable channels.'), - _('Please save this channel backup on another device.'), - _('It may be imported in another Electrum wallet with the same seed.') - ]) + help_text = _(messages.MSG_CREATED_NON_RECOVERABLE_CHANNEL) data = lnworker.export_channel_backup(chan.channel_id) popup = QRDialog( title, data, diff --git a/electrum/gui/messages.py b/electrum/gui/messages.py index 6f98367a3..11715e8b6 100644 --- a/electrum/gui/messages.py +++ b/electrum/gui/messages.py @@ -9,3 +9,13 @@ If this is enabled, other nodes cannot open a channel to you. Channel recovery d """ MSG_REQUEST_FORCE_CLOSE = """If you choose to request force-close, your node will pretend that it has lost its data and ask the remote node to broadcast their latest state. Doing so from time to time helps make sure that nodes are honest, because your node can punish them if they broadcast a revoked state.""" + +MSG_CREATED_NON_RECOVERABLE_CHANNEL = """ +The channel you created is not recoverable from seed. +To prevent fund losses, please save this backup on another device. +It may be imported in another Electrum wallet with the same seed. +""" + +MSG_LIGHTNING_WARNING = """ +Electrum uses static channel backups. If you lose your wallet file, you will need to request your channel to be force-closed by the remote peer in order to recover your funds. This assumes that the remote peer is reachable, and has not lost its own data. +""" diff --git a/electrum/gui/qt/channels_list.py b/electrum/gui/qt/channels_list.py index 52bfeae6c..e45eb1d67 100644 --- a/electrum/gui/qt/channels_list.py +++ b/electrum/gui/qt/channels_list.py @@ -356,15 +356,11 @@ class ChannelsList(MyTreeView): return h def new_channel_with_warning(self): - if not self.parent.wallet.lnworker.channels: - warning = _("Lightning support in Electrum is experimental. " - "Do not put large amounts in lightning channels.") - if not self.parent.wallet.lnworker.has_recoverable_channels(): - warning += _("Funds stored in lightning channels are not recoverable from your seed. " - "You must backup your wallet file everytime you create a new channel.") + lnworker = self.parent.wallet.lnworker + if not lnworker.channels and not lnworker.channel_backups: + warning = _(messages.MSG_LIGHTNING_WARNING) answer = self.parent.question( - _('Do you want to create your first channel?') + '\n\n' + - _('WARNING') + ': ' + '\n\n' + warning) + _('Do you want to create your first channel?') + '\n\n' + warning) if answer: self.new_channel_dialog() else: diff --git a/electrum/gui/qt/main_window.py b/electrum/gui/qt/main_window.py index 964d3c538..cbd91e509 100644 --- a/electrum/gui/qt/main_window.py +++ b/electrum/gui/qt/main_window.py @@ -50,12 +50,13 @@ from PyQt5.QtWidgets import (QMessageBox, QComboBox, QSystemTrayIcon, QTabWidget QMenu, QAction, QStackedWidget, QToolButton) import electrum +from electrum.gui import messages from electrum import (keystore, ecc, constants, util, bitcoin, commands, paymentrequest, lnutil) from electrum.bitcoin import COIN, is_address from electrum.plugin import run_hook, BasePlugin from electrum.i18n import _ -from electrum.util import (format_time, get_backup_dir, +from electrum.util import (format_time, UserCancelled, profiler, bh2u, bfh, InvalidPassword, UserFacingException, @@ -627,7 +628,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger): vbox.addLayout(Buttons(CancelButton(d), OkButton(d))) if not d.exec_(): return False - backup_dir = get_backup_dir(self.config) + backup_dir = self.config.get_backup_dir() if backup_dir is None: self.show_message(_("You need to configure a backup directory in your preferences"), title=_("Backup not configured")) return @@ -1831,24 +1832,37 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger): funding_sat=funding_sat, push_amt_sat=push_amt, password=password) - def on_success(args): - chan, funding_tx = args - n = chan.constraints.funding_txn_minimum_depth - message = '\n'.join([ - _('Channel established.'), - _('Remote peer ID') + ':' + chan.node_id.hex(), - _('This channel will be usable after {} confirmations').format(n) - ]) - if not funding_tx.is_complete(): - message += '\n\n' + _('Please sign and broadcast the funding transaction') - self.show_message(message) - if not funding_tx.is_complete(): - self.show_transaction(funding_tx) - def on_failure(exc_info): type_, e, traceback = exc_info self.show_error(_('Could not open channel: {}').format(repr(e))) - WaitingDialog(self, _('Opening channel...'), task, on_success, on_failure) + WaitingDialog(self, _('Opening channel...'), task, self.on_open_channel_success, on_failure) + + def on_open_channel_success(self, args): + chan, funding_tx = args + lnworker = self.wallet.lnworker + if not lnworker.has_recoverable_channels(): + backup_dir = self.config.get_backup_dir() + if backup_dir is not None: + self.show_message(_(f'Your wallet backup has been updated in {backup_dir}')) + else: + data = lnworker.export_channel_backup(chan.channel_id) + help_text = _(messages.MSG_CREATED_NON_RECOVERABLE_CHANNEL) + self.show_qrcode( + data, _('Save channel backup'), + help_text=help_text, + show_copy_text_btn=True) + n = chan.constraints.funding_txn_minimum_depth + message = '\n'.join([ + _('Channel established.'), + _('Remote peer ID') + ':' + chan.node_id.hex(), + _('This channel will be usable after {} confirmations').format(n) + ]) + if not funding_tx.is_complete(): + message += '\n\n' + _('Please sign and broadcast the funding transaction') + self.show_message(message) + self.show_transaction(funding_tx) + else: + self.show_message(message) def query_choice(self, msg, choices): # Needed by QtHandler for hardware wallets diff --git a/electrum/lnworker.py b/electrum/lnworker.py index 64496bcf2..58adc2d71 100644 --- a/electrum/lnworker.py +++ b/electrum/lnworker.py @@ -26,7 +26,7 @@ from aiorpcx import run_in_thread, TaskGroup, NetAddress, ignore_after from . import constants, util from . import keystore -from .util import profiler, chunks, get_backup_dir +from .util import profiler, chunks from .invoices import PR_TYPE_LN, PR_UNPAID, PR_EXPIRED, PR_PAID, PR_INFLIGHT, PR_FAILED, PR_ROUTING, LNInvoice, LN_EXPIRY_NEVER from .util import NetworkRetryManager, JsonRPCClient from .lnutil import LN_MAX_FUNDING_SAT @@ -1005,7 +1005,7 @@ class LNWallet(LNWorker): self.wallet.set_reserved_state_of_address(addr, reserved=True) try: self.save_channel(chan) - backup_dir = get_backup_dir(self.config) + backup_dir = self.config.get_backup_dir() if backup_dir is not None: self.wallet.save_backup(backup_dir) except: diff --git a/electrum/simple_config.py b/electrum/simple_config.py index ce8a4470c..8b92844fb 100644 --- a/electrum/simple_config.py +++ b/electrum/simple_config.py @@ -266,6 +266,14 @@ class SimpleConfig(Logger): if os.path.exists(self.path): # or maybe not? raise + def get_backup_dir(self): + # this is used to save a backup everytime a channel is created + # on Android, the export backup button uses android_backup_dir() + if 'ANDROID_DATA' in os.environ: + return None + else: + return self.get('backup_dir') + def get_wallet_path(self, *, use_gui_last_wallet=False): """Set the path of the wallet.""" diff --git a/electrum/util.py b/electrum/util.py index 854d7bd99..59fd43393 100644 --- a/electrum/util.py +++ b/electrum/util.py @@ -427,14 +427,6 @@ def android_data_dir(): PythonActivity = jnius.autoclass('org.kivy.android.PythonActivity') return PythonActivity.mActivity.getFilesDir().getPath() + '/data' -def get_backup_dir(config): - # this is used to save a backup everytime a channel is created - # on Android, the export backup button uses android_backup_dir() - if 'ANDROID_DATA' in os.environ: - return None - else: - return config.get('backup_dir') - def ensure_sparse_file(filename): # On modern Linux, no need to do anything. # On Windows, need to explicitly mark file.