diff --git a/contrib/android/p4a_recipes/libsecp256k1/__init__.py b/contrib/android/p4a_recipes/libsecp256k1/__init__.py index 079f7cf09821..4a549bec9a87 100644 --- a/contrib/android/p4a_recipes/libsecp256k1/__init__.py +++ b/contrib/android/p4a_recipes/libsecp256k1/__init__.py @@ -6,9 +6,9 @@ class LibSecp256k1RecipePinned(LibSecp256k1Recipe): - version = "e3a885d42a7800c1ccebad94ad1e2b82c4df5c65" + version = "642c885b6102725e25623738529895a95addc4f4" url = "https://github.com/bitcoin-core/secp256k1/archive/{version}.zip" - sha512sum = "fef2671fcdcf9a1127597b2db0109279c38053b990bd3b979f863fdb3c92e691df9e2d3bdd22e1bb59100b3d27f27b07df1f0e314f2535148bd250665c8f1370" + sha512sum = "81c0048630e4b2ab24a71fc2156ff9f15bc6d379106cbe4724acd18a48269d07df51660662bcea4df167578a43837a8bc27af380f3a37b4c69e30cdd72f2b3fb" recipe = LibSecp256k1RecipePinned() diff --git a/contrib/make_libsecp256k1.sh b/contrib/make_libsecp256k1.sh index 4a9692081a30..d40c1054da62 100755 --- a/contrib/make_libsecp256k1.sh +++ b/contrib/make_libsecp256k1.sh @@ -14,9 +14,10 @@ # sudo apt-get install gcc-multilib g++-multilib # $ AUTOCONF_FLAGS="--host=i686-linux-gnu CFLAGS=-m32 CXXFLAGS=-m32 LDFLAGS=-m32" ./contrib/make_libsecp256k1.sh -LIBSECP_VERSION="e3a885d42a7800c1ccebad94ad1e2b82c4df5c65" -# ^ tag "v0.5.0" +LIBSECP_VERSION="642c885b6102725e25623738529895a95addc4f4" +# ^ tag "v0.5.1" # note: this version is duplicated in contrib/android/p4a_recipes/libsecp256k1/__init__.py +# (and also in electrum-ecc, for the "secp256k1" git submodule) set -e diff --git a/electrum_grs/exchange_rate.py b/electrum_grs/exchange_rate.py index 86bb8d5931e9..fc77f09e2fc3 100644 --- a/electrum_grs/exchange_rate.py +++ b/electrum_grs/exchange_rate.py @@ -260,6 +260,7 @@ def history_ccys(self): return CURRENCIES[self.name()] async def request_history(self, ccy): + # ref https://docs.coingecko.com/v3.0.1/reference/coins-id-market-chart num_days = 365 # Setting `num_days = "max"` started erroring (around 2024-04) with: # > Your request exceeds the allowed time range. Public API users are limited to querying @@ -332,11 +333,14 @@ async def query_all_exchanges_for_their_ccys_over_network(): for name, klass in exchanges.items(): exchange = klass(None, None) await group.spawn(get_currencies_safe(name, exchange)) - loop = util.get_asyncio_loop() + + loop = asyncio.new_event_loop() try: loop.run_until_complete(query_all_exchanges_for_their_ccys_over_network()) except Exception as e: pass + finally: + loop.close() with open(path, 'w', encoding='utf-8') as f: f.write(json.dumps(d, indent=4, sort_keys=True)) return d diff --git a/electrum_grs/gui/qt/confirm_tx_dialog.py b/electrum_grs/gui/qt/confirm_tx_dialog.py index 0130c6c52158..4211891a259a 100644 --- a/electrum_grs/gui/qt/confirm_tx_dialog.py +++ b/electrum_grs/gui/qt/confirm_tx_dialog.py @@ -42,7 +42,7 @@ from electrum_grs.bitcoin import DummyAddress from .util import (WindowModalDialog, ColorScheme, HelpLabel, Buttons, CancelButton, - BlockingWaitingDialog, PasswordLineEdit, WWLabel, read_QIcon) + PasswordLineEdit, WWLabel, read_QIcon) from .fee_slider import FeeSlider, FeeComboBox @@ -532,7 +532,7 @@ def _update_widgets(self): if self.not_enough_funds: self.io_widget.update(None) self.set_feerounding_visibility(False) - self.messages = [] + self.messages = [_('Preparing transaction...')] else: self.messages = self.get_messages() self.update_fee_fields() @@ -619,7 +619,7 @@ def __init__(self, *, window: 'ElectrumWindow', make_tx, output_value: Union[int title=_("New Transaction"), # todo: adapt title for channel funding tx, swaps allow_preview=allow_preview) - BlockingWaitingDialog(window, _("Preparing transaction..."), self.update) + self.trigger_update() def _update_amount_label(self): tx = self.tx diff --git a/electrum_grs/gui/qt/main_window.py b/electrum_grs/gui/qt/main_window.py index e3d26cd85818..7aba850974a9 100644 --- a/electrum_grs/gui/qt/main_window.py +++ b/electrum_grs/gui/qt/main_window.py @@ -90,7 +90,7 @@ import_meta_gui, export_meta_gui, filename_field, address_field, char_width_in_lineedit, webopen, TRANSACTION_FILE_EXTENSION_FILTER_ANY, MONOSPACE_FONT, - getOpenFileName, getSaveFileName, BlockingWaitingDialog, font_height) + getOpenFileName, getSaveFileName, font_height) from .util import ButtonsLineEdit, ShowQRLineEdit from .util import QtEventListener, qt_event_listener, event_listener from .wizard.wallet import WIF_HELP_TEXT @@ -301,26 +301,11 @@ def on_version_received(v): self._update_check_thread.checked.connect(on_version_received) self._update_check_thread.start() - def run_coroutine_dialog(self, coro, text, on_result, on_cancelled): - """ run coroutine in a waiting dialog, with a Cancel button that cancels the coroutine """ - from electrum import util - loop = util.get_asyncio_loop() - assert util.get_running_loop() != loop, 'must not be called from asyncio thread' - future = asyncio.run_coroutine_threadsafe(coro, loop) - def task(): - try: - return future.result() - except concurrent.futures.CancelledError: - on_cancelled() - try: - WaitingDialog( - self, text, task, - on_success=on_result, - on_error=self.on_error, - on_cancel=future.cancel) - except Exception as e: - self.show_error(str(e)) - raise + def run_coroutine_dialog(self, coro, text): + """ run coroutine in a waiting dialog, with a Cancel button that cancels the coroutine""" + from .util import RunCoroutineDialog + d = RunCoroutineDialog(self, text, coro) + return d.run() def run_coroutine_from_thread(self, coro, name, on_result=None): if self._cleaned_up: @@ -1180,10 +1165,9 @@ def run_swap_dialog(self, is_reverse=None, recv_amount_sat=None, channels=None): if not self.wallet.lnworker.num_sats_can_send() and not self.wallet.lnworker.num_sats_can_receive(): self.show_error(_("You do not have liquidity in your active channels.")) return - def get_pairs_thread(): - self.network.run_from_another_thread(self.wallet.lnworker.swap_manager.get_pairs()) try: - BlockingWaitingDialog(self, _('Please wait...'), get_pairs_thread) + self.run_coroutine_dialog( + self.wallet.lnworker.swap_manager.get_pairs(), _('Please wait...')) except SwapServerError as e: self.show_error(str(e)) return diff --git a/electrum_grs/gui/qt/swap_dialog.py b/electrum_grs/gui/qt/swap_dialog.py index 26543d2bbca1..f7207d90770b 100644 --- a/electrum_grs/gui/qt/swap_dialog.py +++ b/electrum_grs/gui/qt/swap_dialog.py @@ -4,7 +4,7 @@ from PyQt6.QtWidgets import QLabel, QVBoxLayout, QGridLayout, QPushButton from electrum_grs.i18n import _ -from electrum_grs.util import NotEnoughFunds, NoDynamicFeeEstimates +from electrum_grs.util import NotEnoughFunds, NoDynamicFeeEstimates, UserCancelled from electrum_grs.bitcoin import DummyAddress from electrum_grs.transaction import PartialTxOutput, PartialTransaction @@ -319,25 +319,33 @@ def update_ok_button(self): recv_amount = self.recv_amount_e.get_amount() self.ok_button.setEnabled(bool(send_amount) and bool(recv_amount)) - def do_normal_swap(self, lightning_amount, onchain_amount, password): + async def _do_normal_swap(self, lightning_amount, onchain_amount, password): dummy_tx = self._create_tx(onchain_amount) assert dummy_tx sm = self.swap_manager + swap, invoice = await sm.request_normal_swap( + lightning_amount_sat=lightning_amount, + expected_onchain_amount_sat=onchain_amount, + channels=self.channels, + ) + self._current_swap = swap + tx = sm.create_funding_tx(swap, dummy_tx, password=password) + txid = await sm.wait_for_htlcs_and_broadcast(swap=swap, invoice=invoice, tx=tx) + return txid + + def do_normal_swap(self, lightning_amount, onchain_amount, password): self._current_swap = None - async def coro(): - swap, invoice = await sm.request_normal_swap( - lightning_amount_sat=lightning_amount, - expected_onchain_amount_sat=onchain_amount, - channels=self.channels, - ) - self._current_swap = swap - tx = sm.create_funding_tx(swap, dummy_tx, password=password) - txid = await sm.wait_for_htlcs_and_broadcast(swap=swap, invoice=invoice, tx=tx) - return txid - self.window.run_coroutine_dialog( - coro(), _('Awaiting swap payment...'), - on_result=lambda funding_txid: self.window.on_swap_result(funding_txid, is_reverse=False), - on_cancelled=lambda: sm.cancel_normal_swap(self._current_swap)) + coro = self._do_normal_swap(lightning_amount, onchain_amount, password) + try: + funding_txid = self.window.run_coroutine_dialog(coro, _('Awaiting swap payment...')) + except UserCancelled: + self.swap_manager.cancel_normal_swap(self._current_swap) + self.window.show_message(_('Swap cancelled')) + return + except Exception as e: + self.window.show_error(str(e)) + return + self.window.on_swap_result(funding_txid, is_reverse=False) def get_description(self): onchain_funds = "onchain funds" diff --git a/electrum_grs/gui/qt/transaction_dialog.py b/electrum_grs/gui/qt/transaction_dialog.py index 5f2508fa9624..8d2de461cc31 100644 --- a/electrum_grs/gui/qt/transaction_dialog.py +++ b/electrum_grs/gui/qt/transaction_dialog.py @@ -62,7 +62,7 @@ char_width_in_lineedit, TRANSACTION_FILE_EXTENSION_FILTER_SEPARATE, TRANSACTION_FILE_EXTENSION_FILTER_ONLY_COMPLETE_TX, TRANSACTION_FILE_EXTENSION_FILTER_ONLY_PARTIAL_TX, - BlockingWaitingDialog, getSaveFileName, ColorSchemeItem, + getSaveFileName, ColorSchemeItem, get_iconname_qrcode, VLine, WaitingDialog) from .rate_limiter import rate_limited from .my_treeview import create_toolbar_with_menu, QMenuWithConfig @@ -582,11 +582,9 @@ def set_tx(self, tx: 'Transaction'): # FIXME for PSBTs, we do a blocking fetch, as the missing data might be needed for e.g. signing # - otherwise, the missing data is for display-completeness only, e.g. fee, input addresses (we do it async) if not tx.is_complete() and tx.is_missing_info_from_network(): - BlockingWaitingDialog( - self, + self.main_window.run_coroutine_dialog( + tx.add_info_from_network(self.wallet.network, timeout=10), _("Adding info to tx, from network..."), - lambda: Network.run_from_another_thread( - tx.add_info_from_network(self.wallet.network, timeout=10)), ) else: self.maybe_fetch_txin_data() diff --git a/electrum_grs/gui/qt/util.py b/electrum_grs/gui/qt/util.py index f8c9d20f61e4..68c792e03f3f 100644 --- a/electrum_grs/gui/qt/util.py +++ b/electrum_grs/gui/qt/util.py @@ -21,7 +21,7 @@ from electrum_grs.i18n import _ from electrum_grs.util import FileImportFailed, FileExportFailed, resource_path -from electrum_grs.util import EventListener, event_listener, get_logger +from electrum_grs.util import EventListener, event_listener, get_logger, UserCancelled from electrum_grs.invoices import PR_UNPAID, PR_PAID, PR_EXPIRED, PR_INFLIGHT, PR_UNKNOWN, PR_FAILED, PR_ROUTING, PR_UNCONFIRMED, PR_BROADCASTING, PR_BROADCAST from electrum_grs.logging import Logger from electrum_grs.qrreader import MissingQrDetectionLib @@ -371,32 +371,32 @@ def update(self, msg): self.message_label.setText(msg) -class BlockingWaitingDialog(WindowModalDialog): - """Shows a waiting dialog whilst running a task. - Should be called from the GUI thread. The GUI thread will be blocked while - the task is running; the point of the dialog is to provide feedback - to the user regarding what is going on. - """ - def __init__(self, parent: QWidget, message: str, task: Callable[[], Any]): - assert parent - if isinstance(parent, MessageBoxMixin): - parent = parent.top_level_window() - WindowModalDialog.__init__(self, parent, _("Please wait")) - self.message_label = QLabel(message) - vbox = QVBoxLayout(self) - vbox.addWidget(self.message_label) - self.finished.connect(self.deleteLater) # see #3956 - # show popup - self.show() - # refresh GUI; needed for popup to appear and for message_label to get drawn - QCoreApplication.processEvents() - QCoreApplication.processEvents() - try: - # block and run given task - task() - finally: - # close popup - self.accept() +class RunCoroutineDialog(WaitingDialog): + + def __init__(self, parent: QWidget, message: str, coroutine): + from electrum import util + import asyncio + import concurrent.futures + loop = util.get_asyncio_loop() + assert util.get_running_loop() != loop, 'must not be called from asyncio thread' + self._exception = None + self._result = None + self._future = asyncio.run_coroutine_threadsafe(coroutine, loop) + def task(): + try: + self._result = self._future.result() + except concurrent.futures.CancelledError: + self._exception = UserCancelled + except Exception as e: + self._exception = e + WaitingDialog.__init__(self, parent, message, task, on_cancel=self._future.cancel) + + def run(self): + self.exec() + if self._exception: + raise self._exception + else: + return self._result def line_dialog(parent, title, label, ok_label, default=None): @@ -1242,14 +1242,21 @@ def getSaveFileName( def icon_path(icon_basename: str): return resource_path('gui', 'icons', icon_basename) +def internal_plugin_icon_path(plugin_name, icon_basename: str): + return resource_path('plugins', plugin_name, icon_basename) + @lru_cache(maxsize=1000) def read_QIcon(icon_basename: str) -> QIcon: return QIcon(icon_path(icon_basename)) -def read_QIcon_from_bytes(b: bytes) -> QIcon: +def read_QPixmap_from_bytes(b: bytes) -> QPixmap: qp = QPixmap() qp.loadFromData(b) + return qp + +def read_QIcon_from_bytes(b: bytes) -> QIcon: + qp = read_QPixmap_from_bytes(b) return QIcon(qp) class IconLabel(QWidget): diff --git a/electrum_grs/interface.py b/electrum_grs/interface.py index 4d55af3de810..5196ae06a03f 100644 --- a/electrum_grs/interface.py +++ b/electrum_grs/interface.py @@ -513,6 +513,9 @@ async def _get_ssl_context(self): else: # pinned self-signed cert sslc = ssl.create_default_context(purpose=ssl.Purpose.SERVER_AUTH, cafile=self.cert_path) + # note: Flag "ssl.VERIFY_X509_STRICT" is enabled by default in python 3.13+ (disabled in older versions). + # We explicitly disable it as it breaks lots of servers. + sslc.verify_flags &= ~ssl.VERIFY_X509_STRICT sslc.check_hostname = False return sslc diff --git a/electrum_grs/lnchannel.py b/electrum_grs/lnchannel.py index 51b0614a56a2..ef29b9e3a11c 100644 --- a/electrum_grs/lnchannel.py +++ b/electrum_grs/lnchannel.py @@ -191,7 +191,6 @@ class AbstractChannel(Logger, ABC): funding_outpoint: Outpoint node_id: bytes # note that it might not be the full 33 bytes; for OCB it is only the prefix _state: ChannelState - sweep_address: str def set_short_channel_id(self, short_id: ShortChannelID) -> None: self.short_channel_id = short_id @@ -225,17 +224,17 @@ def set_state(self, state: ChannelState, *, force: bool = False) -> None: def get_state(self) -> ChannelState: return self._state - def is_funded(self): + def is_funded(self) -> bool: return self.get_state() >= ChannelState.FUNDED - def is_open(self): + def is_open(self) -> bool: return self.get_state() == ChannelState.OPEN - def is_closed(self): + def is_closed(self) -> bool: # the closing txid has been saved return self.get_state() >= ChannelState.CLOSING - def is_redeemed(self): + def is_redeemed(self) -> bool: return self.get_state() == ChannelState.REDEEMED def need_to_subscribe(self) -> bool: @@ -268,7 +267,7 @@ def get_close_options(self) -> Sequence[ChanCloseOption]: def save_funding_height(self, *, txid: str, height: int, timestamp: Optional[int]) -> None: self.storage['funding_height'] = txid, height, timestamp - def get_funding_height(self): + def get_funding_height(self) -> Optional[Tuple[str, int, Optional[int]]]: return self.storage.get('funding_height') def delete_funding_height(self): @@ -277,19 +276,19 @@ def delete_funding_height(self): def save_closing_height(self, *, txid: str, height: int, timestamp: Optional[int]) -> None: self.storage['closing_height'] = txid, height, timestamp - def get_closing_height(self): + def get_closing_height(self) -> Optional[Tuple[str, int, Optional[int]]]: return self.storage.get('closing_height') def delete_closing_height(self): self.storage.pop('closing_height', None) - def create_sweeptxs_for_our_ctx(self, ctx): - return create_sweeptxs_for_our_ctx(chan=self, ctx=ctx, sweep_address=self.sweep_address) + def create_sweeptxs_for_our_ctx(self, ctx: Transaction) -> Optional[Dict[str, SweepInfo]]: + return create_sweeptxs_for_our_ctx(chan=self, ctx=ctx, sweep_address=self.get_sweep_address()) - def create_sweeptxs_for_their_ctx(self, ctx): - return create_sweeptxs_for_their_ctx(chan=self, ctx=ctx, sweep_address=self.sweep_address) + def create_sweeptxs_for_their_ctx(self, ctx: Transaction) -> Optional[Dict[str, SweepInfo]]: + return create_sweeptxs_for_their_ctx(chan=self, ctx=ctx, sweep_address=self.get_sweep_address()) - def is_backup(self): + def is_backup(self) -> bool: return False def get_local_scid_alias(self, *, create_new_if_needed: bool = False) -> Optional[bytes]: @@ -338,7 +337,7 @@ def update_onchain_state(self, *, funding_txid: str, funding_height: TxMinedInfo closing_height=closing_height, keep_watching=keep_watching) - def update_unfunded_state(self): + def update_unfunded_state(self) -> None: self.delete_funding_height() self.delete_closing_height() if self.get_state() in [ChannelState.PREOPENING, ChannelState.OPENING, ChannelState.FORCE_CLOSING] and self.lnworker: @@ -416,6 +415,14 @@ def is_funding_tx_mined(self, funding_height: TxMinedInfo) -> bool: def get_funding_address(self) -> str: pass + @abstractmethod + def get_sweep_address(self) -> str: + """Returns a wallet address we can use to sweep coins to. + It could be something static to the channel (fixed for its lifecycle), + or it might just ask the wallet now for an unused address. + """ + pass + def get_state_for_GUI(self) -> str: cs = self.get_state() if cs <= ChannelState.OPEN and self.unconfirmed_closing_txid: @@ -575,7 +582,7 @@ def create_sweeptxs_for_their_ctx(self, ctx): def create_sweeptxs_for_our_ctx(self, ctx): if self.is_imported: - return create_sweeptxs_for_our_ctx(chan=self, ctx=ctx, sweep_address=self.sweep_address) + return create_sweeptxs_for_our_ctx(chan=self, ctx=ctx, sweep_address=self.get_sweep_address()) else: # backup from op_return return {} @@ -613,8 +620,7 @@ def is_frozen_for_sending(self) -> bool: def is_frozen_for_receiving(self) -> bool: return False - @property - def sweep_address(self) -> str: + def get_sweep_address(self) -> str: return self.lnworker.wallet.get_new_sweep_address_for_channel() def get_local_pubkey(self) -> bytes: @@ -812,7 +818,7 @@ def get_outgoing_gossip_channel_update(self, *, scid: ShortChannelID = None) -> self._outgoing_channel_update = chan_upd return chan_upd - def construct_channel_announcement_without_sigs(self) -> bytes: + def construct_channel_announcement_without_sigs(self) -> Tuple[bytes, bool]: bitcoin_keys = [ self.config[REMOTE].multisig_key.pubkey, self.config[LOCAL].multisig_key.pubkey] @@ -846,10 +852,8 @@ def is_zeroconf(self) -> bool: channel_type = ChannelType(self.storage.get('channel_type')) return bool(channel_type & ChannelType.OPTION_ZEROCONF) - @property - def sweep_address(self) -> str: + def get_sweep_address(self) -> str: # TODO: in case of unilateral close with pending HTLCs, this address will be reused - addr = None assert self.is_static_remotekey_enabled() our_payment_pubkey = self.config[LOCAL].payment_basepoint.pubkey addr = make_commitment_output_to_remote_address(our_payment_pubkey) @@ -1415,10 +1419,10 @@ def get_oldest_unrevoked_commitment(self, subject: HTLCOwner) -> PartialTransact ctn = self.get_oldest_unrevoked_ctn(subject) return self.get_commitment(subject, ctn=ctn) - def create_sweeptxs(self, ctn: int) -> List[Transaction]: + def create_sweeptxs_for_watchtower(self, ctn: int) -> List[Transaction]: from .lnsweep import create_sweeptxs_for_watchtower secret, ctx = self.get_secret_and_commitment(REMOTE, ctn=ctn) - return create_sweeptxs_for_watchtower(self, ctx, secret, self.sweep_address) + return create_sweeptxs_for_watchtower(self, ctx, secret, self.get_sweep_address()) def get_oldest_unrevoked_ctn(self, subject: HTLCOwner) -> int: return self.hm.ctn_oldest_unrevoked(subject) @@ -1645,7 +1649,7 @@ def get_close_options(self) -> Sequence[ChanCloseOption]: def maybe_sweep_revoked_htlc(self, ctx: Transaction, htlc_tx: Transaction) -> Optional[SweepInfo]: # look at the output address, check if it matches - return create_sweeptx_for_their_revoked_htlc(self, ctx, htlc_tx, self.sweep_address) + return create_sweeptx_for_their_revoked_htlc(self, ctx, htlc_tx, self.get_sweep_address()) def has_pending_changes(self, subject: HTLCOwner) -> bool: next_htlcs = self.hm.get_htlcs_in_next_ctx(subject) diff --git a/electrum_grs/lnpeer.py b/electrum_grs/lnpeer.py index 7261689fbc2d..cbbeab6418e8 100644 --- a/electrum_grs/lnpeer.py +++ b/electrum_grs/lnpeer.py @@ -1394,6 +1394,7 @@ def resend_revoke_and_ack(): self.send_channel_ready(chan) self.maybe_send_announcement_signatures(chan) + self.maybe_update_fee(chan) # if needed, update fee ASAP, to avoid force-closures from this # checks done util.trigger_callback('channel', self.lnworker.wallet, chan) # if we have sent a previous shutdown, it must be retransmitted (Bolt2) @@ -2263,6 +2264,8 @@ def maybe_update_fee(self, chan: Channel): """ if not chan.can_send_ctx_updates(): return + if chan.get_state() != ChannelState.OPEN: + return feerate_per_kw = self.lnworker.current_target_feerate_per_kw() def does_chan_fee_need_update(chan_feerate: Union[float, int]) -> bool: # We raise fees more aggressively than we lower them. Overpaying is not too bad, @@ -2374,7 +2377,7 @@ async def send_shutdown(self, chan: Channel): if chan.config[LOCAL].upfront_shutdown_script: scriptpubkey = chan.config[LOCAL].upfront_shutdown_script else: - scriptpubkey = bitcoin.address_to_script(chan.sweep_address) + scriptpubkey = bitcoin.address_to_script(chan.get_sweep_address()) assert scriptpubkey # wait until no more pending updates (bolt2) chan.set_can_send_ctx_updates(False) @@ -2427,7 +2430,7 @@ async def _shutdown(self, chan: Channel, payload, *, is_local: bool): if chan.config[LOCAL].upfront_shutdown_script: our_scriptpubkey = chan.config[LOCAL].upfront_shutdown_script else: - our_scriptpubkey = bitcoin.address_to_script(chan.sweep_address) + our_scriptpubkey = bitcoin.address_to_script(chan.get_sweep_address()) assert our_scriptpubkey # estimate fee of closing tx dummy_sig, dummy_tx = chan.make_closing_tx(our_scriptpubkey, their_scriptpubkey, fee_sat=0) diff --git a/electrum_grs/lnsweep.py b/electrum_grs/lnsweep.py index 74825e455737..3b653584a638 100644 --- a/electrum_grs/lnsweep.py +++ b/electrum_grs/lnsweep.py @@ -344,7 +344,7 @@ def analyze_ctx(chan: 'Channel', ctx: Transaction): def create_sweeptxs_for_their_ctx( *, chan: 'Channel', ctx: Transaction, - sweep_address: str) -> Optional[Dict[str,SweepInfo]]: + sweep_address: str) -> Optional[Dict[str, SweepInfo]]: """Handle the case when the remote force-closes with their ctx. Sweep outputs that do not have a CSV delay ('to_remote' and first-stage HTLCs). Outputs with CSV delay ('to_local' and second-stage HTLCs) are redeemed by LNWatcher. @@ -376,7 +376,7 @@ def create_sweeptxs_for_their_ctx( chan.logger.debug(f'(lnsweep) found their ctx: {to_local_address} {to_remote_address}') if is_revocation: our_revocation_privkey = derive_blinded_privkey(our_conf.revocation_basepoint.privkey, per_commitment_secret) - gen_tx = create_sweeptx_for_their_revoked_ctx(chan, ctx, per_commitment_secret, chan.sweep_address) + gen_tx = create_sweeptx_for_their_revoked_ctx(chan, ctx, per_commitment_secret, sweep_address) if gen_tx: tx = gen_tx() txs[tx.inputs()[0].prevout.to_str()] = SweepInfo( diff --git a/electrum_grs/lnworker.py b/electrum_grs/lnworker.py index f0b3da3bb5f3..92097fbf2be3 100644 --- a/electrum_grs/lnworker.py +++ b/electrum_grs/lnworker.py @@ -916,7 +916,7 @@ async def sync_channel_with_watchtower(self, chan: Channel, watchtower): current_ctn = chan.get_oldest_unrevoked_ctn(REMOTE) watchtower_ctn = await watchtower.get_ctn(outpoint, addr) for ctn in range(watchtower_ctn + 1, current_ctn): - sweeptxs = chan.create_sweeptxs(ctn) + sweeptxs = chan.create_sweeptxs_for_watchtower(ctn) for tx in sweeptxs: await watchtower.add_sweep_tx(outpoint, ctn, tx.inputs()[0].prevout.to_str(), tx.serialize()) diff --git a/electrum_grs/plugin.py b/electrum_grs/plugin.py index e28428dbd477..8edbc8a956ff 100644 --- a/electrum_grs/plugin.py +++ b/electrum_grs/plugin.py @@ -226,7 +226,7 @@ def find_external_plugins(self): def load_external_plugins(self): for name, d in self.external_plugin_metadata.items(): - if not d.get('requires_wallet_type') and self.config.get('enable_plugin_' + name): + if self.config.get('enable_plugin_' + name): try: self.load_external_plugin(name) except BaseException as e: @@ -441,13 +441,16 @@ def settings_dialog(self, window): raise NotImplementedError() def read_file(self, filename: str) -> bytes: - """ note: only for external plugins """ import zipfile - plugin_filename = self.parent.external_plugin_path(self.name) - with zipfile.ZipFile(plugin_filename) as myzip: - with myzip.open(os.path.join(self.name, filename)) as myfile: - s = myfile.read() - return s + if self.name in self.parent.external_plugin_metadata: + plugin_filename = self.parent.external_plugin_path(self.name) + with zipfile.ZipFile(plugin_filename) as myzip: + with myzip.open(os.path.join(self.name, filename)) as myfile: + return myfile.read() + else: + path = os.path.join(os.path.dirname(__file__), 'plugins', self.name, filename) + with open(path, 'rb') as myfile: + return myfile.read() class DeviceUnpairableError(UserFacingException): pass diff --git a/electrum_grs/plugins/bitbox02/bitbox02.png b/electrum_grs/plugins/bitbox02/bitbox02.png new file mode 100644 index 000000000000..3900c5425ef5 Binary files /dev/null and b/electrum_grs/plugins/bitbox02/bitbox02.png differ diff --git a/electrum_grs/plugins/bitbox02/bitbox02_unpaired.png b/electrum_grs/plugins/bitbox02/bitbox02_unpaired.png new file mode 100644 index 000000000000..66fe1109208c Binary files /dev/null and b/electrum_grs/plugins/bitbox02/bitbox02_unpaired.png differ diff --git a/electrum_grs/plugins/coldcard/coldcard.png b/electrum_grs/plugins/coldcard/coldcard.png new file mode 100644 index 000000000000..8d2c7f9248f3 Binary files /dev/null and b/electrum_grs/plugins/coldcard/coldcard.png differ diff --git a/electrum_grs/plugins/coldcard/coldcard.svg b/electrum_grs/plugins/coldcard/coldcard.svg new file mode 100644 index 000000000000..0f36248769e0 --- /dev/null +++ b/electrum_grs/plugins/coldcard/coldcard.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/electrum_grs/plugins/coldcard/coldcard_unpaired.png b/electrum_grs/plugins/coldcard/coldcard_unpaired.png new file mode 100644 index 000000000000..c8aaf531a8cd Binary files /dev/null and b/electrum_grs/plugins/coldcard/coldcard_unpaired.png differ diff --git a/electrum_grs/plugins/coldcard/coldcard_unpaired.svg b/electrum_grs/plugins/coldcard/coldcard_unpaired.svg new file mode 100644 index 000000000000..c4c7b5da19ab --- /dev/null +++ b/electrum_grs/plugins/coldcard/coldcard_unpaired.svg @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/electrum_grs/plugins/digitalbitbox/digitalbitbox.png b/electrum_grs/plugins/digitalbitbox/digitalbitbox.png new file mode 100644 index 000000000000..ed6d72e6db53 Binary files /dev/null and b/electrum_grs/plugins/digitalbitbox/digitalbitbox.png differ diff --git a/electrum_grs/plugins/digitalbitbox/digitalbitbox_unpaired.png b/electrum_grs/plugins/digitalbitbox/digitalbitbox_unpaired.png new file mode 100644 index 000000000000..2e88ed48866f Binary files /dev/null and b/electrum_grs/plugins/digitalbitbox/digitalbitbox_unpaired.png differ diff --git a/electrum_grs/plugins/hw_wallet/qt.py b/electrum_grs/plugins/hw_wallet/qt.py index f91c5001afec..499b6516ea8e 100644 --- a/electrum_grs/plugins/hw_wallet/qt.py +++ b/electrum_grs/plugins/hw_wallet/qt.py @@ -36,6 +36,7 @@ Buttons, CancelButton, TaskThread, char_width_in_lineedit, PasswordLineEdit) from electrum_grs.gui.qt.main_window import StatusBarButton +from electrum_grs.gui.qt.util import read_QIcon_from_bytes from electrum_grs.i18n import _ from electrum_grs.logging import Logger @@ -92,8 +93,9 @@ def update_status(self, paired): def _update_status(self, paired): if hasattr(self, 'button'): button = self.button - icon_name = button.icon_paired if paired else button.icon_unpaired - button.setIcon(read_QIcon(icon_name)) + icon_bytes = button.icon_paired if paired else button.icon_unpaired + icon = read_QIcon_from_bytes(icon_bytes) + button.setIcon(icon) def query_choice(self, msg: str, labels: Sequence[Tuple]): self.done.clear() @@ -222,9 +224,10 @@ def load_wallet(self: Union['QtPluginBase', HW_PluginBase], wallet: 'Abstract_Wa tooltip = self.device + '\n' + (keystore.label or 'unnamed') cb = partial(self._on_status_bar_button_click, window=window, keystore=keystore) sb = window.statusBar() - button = StatusBarButton(read_QIcon(self.icon_unpaired), tooltip, cb, sb.height()) - button.icon_paired = self.icon_paired - button.icon_unpaired = self.icon_unpaired + icon = read_QIcon_from_bytes(self.read_file(self.icon_unpaired)) + button = StatusBarButton(icon, tooltip, cb, sb.height()) + button.icon_paired = self.read_file(self.icon_paired) + button.icon_unpaired = self.read_file(self.icon_unpaired) sb.addPermanentWidget(button) handler = self.create_handler(window) handler.button = button diff --git a/electrum_grs/plugins/jade/jade.png b/electrum_grs/plugins/jade/jade.png new file mode 100644 index 000000000000..d4cc7f18194c Binary files /dev/null and b/electrum_grs/plugins/jade/jade.png differ diff --git a/electrum_grs/plugins/jade/jade_unpaired.png b/electrum_grs/plugins/jade/jade_unpaired.png new file mode 100644 index 000000000000..ec4789a9175e Binary files /dev/null and b/electrum_grs/plugins/jade/jade_unpaired.png differ diff --git a/electrum_grs/plugins/keepkey/keepkey.png b/electrum_grs/plugins/keepkey/keepkey.png new file mode 100644 index 000000000000..dd5c244fc7d8 Binary files /dev/null and b/electrum_grs/plugins/keepkey/keepkey.png differ diff --git a/electrum_grs/plugins/keepkey/keepkey_unpaired.png b/electrum_grs/plugins/keepkey/keepkey_unpaired.png new file mode 100644 index 000000000000..ac15d2e85595 Binary files /dev/null and b/electrum_grs/plugins/keepkey/keepkey_unpaired.png differ diff --git a/electrum_grs/plugins/ledger/ledger.png b/electrum_grs/plugins/ledger/ledger.png new file mode 100644 index 000000000000..bab69ec52d26 Binary files /dev/null and b/electrum_grs/plugins/ledger/ledger.png differ diff --git a/electrum_grs/plugins/ledger/ledger_unpaired.png b/electrum_grs/plugins/ledger/ledger_unpaired.png new file mode 100644 index 000000000000..65db1ea87587 Binary files /dev/null and b/electrum_grs/plugins/ledger/ledger_unpaired.png differ diff --git a/electrum_grs/plugins/revealer/qt.py b/electrum_grs/plugins/revealer/qt.py index 95e71634d742..4179c470413b 100644 --- a/electrum_grs/plugins/revealer/qt.py +++ b/electrum_grs/plugins/revealer/qt.py @@ -25,17 +25,18 @@ from PyQt6.QtPrintSupport import QPrinter from PyQt6.QtCore import Qt, QRectF, QRect, QSizeF, QUrl, QPoint, QSize, QMarginsF from PyQt6.QtGui import (QPixmap, QImage, QBitmap, QPainter, QFontDatabase, QPen, QFont, - QColor, QDesktopServices, qRgba, QPainterPath, QPageSize) + QColor, QDesktopServices, qRgba, QPainterPath, QPageSize, QPageLayout) from PyQt6.QtWidgets import (QGridLayout, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QLineEdit) from electrum_grs.plugin import hook from electrum_grs.i18n import _ from electrum_grs.util import make_dir, InvalidPassword, UserCancelled -from electrum_grs.gui.qt.util import (read_QIcon, EnterButton, WWLabel, icon_path, +from electrum_grs.gui.qt.util import (read_QIcon, EnterButton, WWLabel, icon_path, internal_plugin_icon_path, WindowModalDialog, Buttons, CloseButton, OkButton) from electrum_grs.gui.qt.qrtextedit import ScanQRTextEdit from electrum_grs.gui.qt.main_window import StatusBarButton +from electrum_grs.gui.qt.util import read_QIcon_from_bytes, read_QPixmap_from_bytes from .revealer import RevealerPlugin @@ -69,6 +70,7 @@ def __init__(self, parent, config, name): self.extension = False self._init_qt_received = False + self.icon_bytes = self.read_file("revealer.png") @hook def init_qt(self, gui: 'ElectrumGui'): @@ -81,8 +83,10 @@ def init_qt(self, gui: 'ElectrumGui'): @hook def create_status_bar(self, sb): - b = StatusBarButton(read_QIcon('revealer.png'), "Revealer "+_("Visual Cryptography Plugin"), - partial(self.setup_dialog, sb), sb.height()) + b = StatusBarButton( + read_QIcon_from_bytes(self.icon_bytes), + "Revealer "+_("Visual Cryptography Plugin"), + partial(self.setup_dialog, sb), sb.height()) sb.addPermanentWidget(b) def requires_settings(self): @@ -125,7 +129,7 @@ def setup_dialog(self, window): logo_label = QLabel() # Set the logo label pixmap. - logo_label.setPixmap(QPixmap(icon_path('revealer.png'))) + logo_label.setPixmap(read_QPixmap_from_bytes(self.icon_bytes)) # Align the logo label to the top left. logo_label.setAlignment(Qt.AlignmentFlag.AlignLeft) @@ -308,7 +312,7 @@ def cypherseed_dialog(self, window): logo_label = QLabel() # Set the logo label pixmap. - logo_label.setPixmap(QPixmap(icon_path('revealer.png'))) + logo_label.setPixmap(read_QPixmap_from_bytes(self.icon_bytes)) # Align the logo label to the top left. logo_label.setAlignment(Qt.AlignmentFlag.AlignLeft) @@ -491,7 +495,7 @@ def make_cypherseed(self, img, rawnoise, calibration=False, is_seed = True): img = img.convertToFormat(QImage.Format.Format_Mono) p = QPainter() p.begin(img) - p.setCompositionMode(26) #xor + p.setCompositionMode(QPainter.CompositionMode.RasterOp_SourceXorDestination) #xor p.drawImage(0, 0, rawnoise) p.end() cypherseed = self.pixelcode_2x2(img) @@ -527,11 +531,11 @@ def calibration(self): def toPdf(self, image): printer = QPrinter() - printer.setPageSize(QPageSize(QSizeF(210, 297), QPrinter.Unit.Millimeter)) + printer.setPageSize(QPageSize(QSizeF(210, 297), QPageSize.Unit.Millimeter)) printer.setResolution(600) printer.setOutputFormat(QPrinter.OutputFormat.PdfFormat) printer.setOutputFileName(self.get_path_to_revealer_file('.pdf')) - printer.setPageMargins(QMarginsF(0, 0, 0, 0), QPrinter.Unit.DevicePixel) + printer.setPageMargins(QMarginsF(0, 0, 0, 0), QPageLayout.Unit.Millimeter) painter = QPainter() painter.begin(printer) @@ -552,11 +556,11 @@ def toPdf(self, image): def calibration_pdf(self, image): printer = QPrinter() - printer.setPageSize(QPageSize(QSizeF(210, 297), QPrinter.Unit.Millimeter)) + printer.setPageSize(QPageSize(QSizeF(210, 297), QPageSize.Unit.Millimeter)) printer.setResolution(600) printer.setOutputFormat(QPrinter.OutputFormat.PdfFormat) printer.setOutputFileName(self.get_path_to_calibration_file()) - printer.setPageMargins(QMarginsF(0, 0, 0, 0), QPrinter.Unit.DevicePixel) + printer.setPageMargins(QMarginsF(0, 0, 0, 0), QPageLayout.Unit.Millimeter) painter = QPainter() painter.begin(printer) @@ -605,6 +609,7 @@ def overlay_marks(self, img, is_cseed=False, calibration_sheet=False): img = QImage(img) painter = QPainter() + painter.setRenderHint(QPainter.RenderHint.SmoothPixmapTransform) painter.begin(base_img) total_distance_h = round(base_img.width() / self.abstand_v) @@ -670,7 +675,7 @@ def overlay_marks(self, img, is_cseed=False, calibration_sheet=False): painter.drawLine(base_img.width()-(dist_h), 0, base_img.width()-(dist_h), base_img.height()) painter.drawImage(((total_distance_h))+11, ((total_distance_h))+11, - QImage(icon_path('electrumb.png')).scaledToWidth(round(2.1*total_distance_h), Qt.TransformationMode.SmoothTransformation)) + QImage(internal_plugin_icon_path(self.name, 'electrumb.png')).scaledToWidth(round(2.1*total_distance_h), Qt.TransformationMode.SmoothTransformation)) painter.setPen(QPen(Qt.GlobalColor.white, border_thick*8)) painter.drawLine(int(base_img.width()-total_distance_h-(border_thick*8)/2-(border_thick/2)-2), @@ -696,8 +701,8 @@ def overlay_marks(self, img, is_cseed=False, calibration_sheet=False): painter.drawLine(dist_h, 0, dist_h, base_img.height()) painter.drawLine(0, base_img.height()-dist_v, base_img.width(), base_img.height()-(dist_v)) painter.drawLine(base_img.width()-(dist_h), 0, base_img.width()-(dist_h), base_img.height()) - logo = QImage(icon_path('revealer_c.png')).scaledToWidth(round(1.3*(total_distance_h))) - painter.drawImage(int(total_distance_h+border_thick), int(total_distance_h+border_thick), logo, Qt.TransformationMode.SmoothTransformation) + logo = QImage(internal_plugin_icon_path(self.name, 'revealer_c.png')).scaledToWidth(round(1.3*(total_distance_h))) + painter.drawImage(int(total_distance_h+border_thick), int(total_distance_h+border_thick), logo) #frame around logo painter.setPen(QPen(Qt.GlobalColor.black, border_thick)) diff --git a/electrum_grs/plugins/revealer/revealer.png b/electrum_grs/plugins/revealer/revealer.png new file mode 100644 index 000000000000..9caef09dde2b Binary files /dev/null and b/electrum_grs/plugins/revealer/revealer.png differ diff --git a/electrum_grs/plugins/safe_t/safe-t.png b/electrum_grs/plugins/safe_t/safe-t.png new file mode 100644 index 000000000000..7a574dec86a9 Binary files /dev/null and b/electrum_grs/plugins/safe_t/safe-t.png differ diff --git a/electrum_grs/plugins/safe_t/safe-t_unpaired.png b/electrum_grs/plugins/safe_t/safe-t_unpaired.png new file mode 100644 index 000000000000..c67a344f1aaa Binary files /dev/null and b/electrum_grs/plugins/safe_t/safe-t_unpaired.png differ diff --git a/electrum_grs/plugins/trezor/trezor.png b/electrum_grs/plugins/trezor/trezor.png new file mode 100644 index 000000000000..3edee94f8374 Binary files /dev/null and b/electrum_grs/plugins/trezor/trezor.png differ diff --git a/electrum_grs/plugins/trezor/trezor_unpaired.png b/electrum_grs/plugins/trezor/trezor_unpaired.png new file mode 100644 index 000000000000..c9854be1af12 Binary files /dev/null and b/electrum_grs/plugins/trezor/trezor_unpaired.png differ diff --git a/electrum_grs/plugins/trustedcoin/qml/Disclaimer.qml b/electrum_grs/plugins/trustedcoin/qml/Disclaimer.qml index bf852007442b..5d2f90187003 100644 --- a/electrum_grs/plugins/trustedcoin/qml/Disclaimer.qml +++ b/electrum_grs/plugins/trustedcoin/qml/Disclaimer.qml @@ -17,7 +17,7 @@ WizardComponent { Image { Layout.alignment: Qt.AlignHCenter Layout.bottomMargin: constants.paddingLarge - source: '../../../gui/icons/trustedcoin-wizard.png' + source: '../trustedcoin-wizard.png' } Label { diff --git a/electrum_grs/plugins/trustedcoin/qt.py b/electrum_grs/plugins/trustedcoin/qt.py index 579f58e8b250..05bc619a731c 100644 --- a/electrum_grs/plugins/trustedcoin/qt.py +++ b/electrum_grs/plugins/trustedcoin/qt.py @@ -40,13 +40,14 @@ from electrum_grs import keystore from electrum_grs.gui.qt.util import (read_QIcon, WindowModalDialog, WaitingDialog, OkButton, - CancelButton, Buttons, icon_path, WWLabel, CloseButton, ColorScheme, + CancelButton, Buttons, icon_path, internal_plugin_icon_path, WWLabel, CloseButton, ColorScheme, ChoiceWidget, PasswordLineEdit, char_width_in_lineedit) from electrum_grs.gui.qt.qrcodewidget import QRCodeWidget from electrum_grs.gui.qt.amountedit import AmountEdit from electrum_grs.gui.qt.main_window import StatusBarButton from electrum_grs.gui.qt.wizard.wallet import WCCreateSeed, WCConfirmSeed, WCHaveSeed, WCEnterExt, WCConfirmExt from electrum_grs.gui.qt.wizard.wizard import WizardComponent +from electrum_grs.gui.qt.util import read_QIcon_from_bytes from .common_qt import TrustedcoinPluginQObject from .trustedcoin import TrustedCoinPlugin, server, DISCLAIMER @@ -103,10 +104,10 @@ def load_wallet(self, wallet: 'Abstract_Wallet', window: 'ElectrumWindow'): _('Therefore, two-factor authentication is disabled.') ]) action = lambda: window.show_message(msg) - icon = read_QIcon("trustedcoin-status-disabled.png") + icon = read_QIcon_from_bytes(self.read_file("trustedcoin-status-disabled.png")) else: action = partial(self.settings_dialog, window) - icon = read_QIcon("trustedcoin-status.png") + icon = read_QIcon_from_bytes(self.read_file("trustedcoin-status.png")) sb = window.statusBar() button = StatusBarButton(icon, _("TrustedCoin"), action, sb.height()) sb.addPermanentWidget(button) @@ -166,6 +167,9 @@ def settings_dialog(self, window): self.waiting_dialog_for_billing_info(window, on_finished=partial(self.show_settings_dialog, window)) + def icon_path(self, name): + return internal_plugin_icon_path(self.name, name) + def show_settings_dialog(self, window, success): if not success: window.show_message(_('Server not reachable.')) @@ -178,7 +182,7 @@ def show_settings_dialog(self, window, success): hbox = QHBoxLayout() logo = QLabel() - logo.setPixmap(QPixmap(icon_path("trustedcoin-status.png"))) + logo.setPixmap(QPixmap(self.icon_path("trustedcoin-status.png"))) msg = _('This wallet is protected by TrustedCoin\'s two-factor authentication.') + '
'\ + _("For more information, visit") + " https://api.trustedcoin.com/#/electrum-help" label = QLabel(msg) @@ -228,46 +232,46 @@ def init_wallet_wizard(self, wizard: 'QENewWalletWizard'): wizard.trustedcoin_qhelper = TrustedcoinPluginQObject(self, wizard, None) self.extend_wizard(wizard) if wizard.start_viewstate and wizard.start_viewstate.view.startswith('trustedcoin_'): - wizard.start_viewstate.params.update({'icon': icon_path('trustedcoin-wizard.png')}) + wizard.start_viewstate.params.update({'icon': self.icon_path('trustedcoin-wizard.png')}) def extend_wizard(self, wizard: 'QENewWalletWizard'): super().extend_wizard(wizard) views = { 'trustedcoin_start': { 'gui': WCDisclaimer, - 'params': {'icon': icon_path('trustedcoin-wizard.png')}, + 'params': {'icon': self.icon_path('trustedcoin-wizard.png')}, }, 'trustedcoin_choose_seed': { 'gui': WCChooseSeed, - 'params': {'icon': icon_path('trustedcoin-wizard.png')}, + 'params': {'icon': self.icon_path('trustedcoin-wizard.png')}, }, 'trustedcoin_create_seed': { 'gui': WCCreateSeed, - 'params': {'icon': icon_path('trustedcoin-wizard.png')}, + 'params': {'icon': self.icon_path('trustedcoin-wizard.png')}, }, 'trustedcoin_confirm_seed': { 'gui': WCConfirmSeed, - 'params': {'icon': icon_path('trustedcoin-wizard.png')}, + 'params': {'icon': self.icon_path('trustedcoin-wizard.png')}, }, 'trustedcoin_have_seed': { 'gui': WCHaveSeed, - 'params': {'icon': icon_path('trustedcoin-wizard.png')}, + 'params': {'icon': self.icon_path('trustedcoin-wizard.png')}, }, 'trustedcoin_keep_disable': { 'gui': WCKeepDisable, - 'params': {'icon': icon_path('trustedcoin-wizard.png')}, + 'params': {'icon': self.icon_path('trustedcoin-wizard.png')}, }, 'trustedcoin_tos': { 'gui': WCTerms, - 'params': {'icon': icon_path('trustedcoin-wizard.png')}, + 'params': {'icon': self.icon_path('trustedcoin-wizard.png')}, }, 'trustedcoin_keystore_unlock': { 'gui': WCKeystorePassword, - 'params': {'icon': icon_path('trustedcoin-wizard.png')}, + 'params': {'icon': self.icon_path('trustedcoin-wizard.png')}, }, 'trustedcoin_show_confirm_otp': { 'gui': WCShowConfirmOTP, - 'params': {'icon': icon_path('trustedcoin-wizard.png')}, + 'params': {'icon': self.icon_path('trustedcoin-wizard.png')}, } } wizard.navmap_merge(views) @@ -279,7 +283,7 @@ def extend_wizard(self, wizard: 'QENewWalletWizard'): }, 'trustedcoin_create_ext': { 'gui': WCEnterExt, - 'params': {'icon': icon_path('trustedcoin-wizard.png')}, + 'params': {'icon': self.icon_path('trustedcoin-wizard.png')}, 'next': 'trustedcoin_confirm_seed', }, 'trustedcoin_confirm_seed': { @@ -287,7 +291,7 @@ def extend_wizard(self, wizard: 'QENewWalletWizard'): }, 'trustedcoin_confirm_ext': { 'gui': WCConfirmExt, - 'params': {'icon': icon_path('trustedcoin-wizard.png')}, + 'params': {'icon': self.icon_path('trustedcoin-wizard.png')}, 'next': 'trustedcoin_tos', }, 'trustedcoin_have_seed': { @@ -295,7 +299,7 @@ def extend_wizard(self, wizard: 'QENewWalletWizard'): }, 'trustedcoin_have_ext': { 'gui': WCEnterExt, - 'params': {'icon': icon_path('trustedcoin-wizard.png')}, + 'params': {'icon': self.icon_path('trustedcoin-wizard.png')}, 'next': 'trustedcoin_keep_disable', }, } @@ -305,7 +309,7 @@ def extend_wizard(self, wizard: 'QENewWalletWizard'): ext_online = { 'trustedcoin_continue_online': { 'gui': WCContinueOnline, - 'params': {'icon': icon_path('trustedcoin-wizard.png')}, + 'params': {'icon': self.icon_path('trustedcoin-wizard.png')}, 'next': lambda d: 'trustedcoin_tos' if d['trustedcoin_go_online'] else 'wallet_password', 'accept': self.on_continue_online, 'last': lambda d: not d['trustedcoin_go_online'] and wizard.is_single_password() diff --git a/electrum_grs/plugins/trustedcoin/trustedcoin-status-disabled.png b/electrum_grs/plugins/trustedcoin/trustedcoin-status-disabled.png new file mode 100644 index 000000000000..eeb3b801bde0 Binary files /dev/null and b/electrum_grs/plugins/trustedcoin/trustedcoin-status-disabled.png differ diff --git a/electrum_grs/plugins/trustedcoin/trustedcoin-status.png b/electrum_grs/plugins/trustedcoin/trustedcoin-status.png new file mode 100644 index 000000000000..ee920c13e0bc Binary files /dev/null and b/electrum_grs/plugins/trustedcoin/trustedcoin-status.png differ diff --git a/electrum_grs/plugins/trustedcoin/trustedcoin-wizard.png b/electrum_grs/plugins/trustedcoin/trustedcoin-wizard.png new file mode 100644 index 000000000000..19f4f6e72db9 Binary files /dev/null and b/electrum_grs/plugins/trustedcoin/trustedcoin-wizard.png differ diff --git a/electrum_grs/plugins/trustedcoin/trustedcoin.py b/electrum_grs/plugins/trustedcoin/trustedcoin.py index b4035d9dc33a..942602aabaec 100644 --- a/electrum_grs/plugins/trustedcoin/trustedcoin.py +++ b/electrum_grs/plugins/trustedcoin/trustedcoin.py @@ -599,7 +599,7 @@ def extend_wizard(self, wizard: 'NewWalletWizard'): 'last': lambda d: wizard.is_single_password() and d['trustedcoin_keepordisable'] == 'disable' }, 'trustedcoin_tos': { - 'next': lambda d: 'trustedcoin_show_confirm_otp' if is_xprv(d['xprv1']) + 'next': lambda d: 'trustedcoin_show_confirm_otp' if 'xprv1' not in d or is_xprv(d['xprv1']) else 'trustedcoin_keystore_unlock' }, 'trustedcoin_keystore_unlock': { diff --git a/tests/test_lnpeer.py b/tests/test_lnpeer.py index 665e1f1ac7b2..5db5707618a1 100644 --- a/tests/test_lnpeer.py +++ b/tests/test_lnpeer.py @@ -24,6 +24,7 @@ from electrum_grs import simple_config, lnutil from electrum_grs.lnaddr import lnencode, LnAddr, lndecode from electrum_grs.bitcoin import COIN, sha256 +from electrum_grs.transaction import Transaction from electrum_grs.util import NetworkRetryManager, bfh, OldTaskGroup, EventListener, InvoiceError from electrum_grs.lnpeer import Peer from electrum_grs.lnutil import LNPeerAddr, Keypair, privkey_to_pubkey @@ -147,7 +148,7 @@ def __init__(self, *, local_keypair: Keypair, chans: Iterable['Channel'], tx_que Logger.__init__(self) NetworkRetryManager.__init__(self, max_retry_delay_normal=1, init_retry_delay_normal=1) self.node_keypair = local_keypair - self.payment_secret_key = os.urandom(256) # does not need to be deterministic in tests + self.payment_secret_key = os.urandom(32) # does not need to be deterministic in tests self._user_dir = tempfile.mkdtemp(prefix="electrum-lnpeer-test-") self.config = SimpleConfig({}, read_user_dir_function=lambda: self._user_dir) self.network = MockNetwork(tx_queue, config=self.config) @@ -312,6 +313,8 @@ async def create_routes_from_invoice(self, amount_msat: int, decoded_invoice: Ln save_forwarding_failure = LNWallet.save_forwarding_failure get_forwarding_failure = LNWallet.get_forwarding_failure maybe_cleanup_forwarding = LNWallet.maybe_cleanup_forwarding + current_target_feerate_per_kw = LNWallet.current_target_feerate_per_kw + current_low_feerate_per_kw = LNWallet.current_low_feerate_per_kw class MockTransport: @@ -1228,7 +1231,7 @@ async def close(): await util.wait_for2(p2.initialized, 1) # bob closes channel with different shutdown script await p1.close_channel(alice_channel.channel_id) - gath.cancel() + self.fail("p1.close_channel should have raised above!") async def main_loop(peer): async with peer.taskgroup as group: @@ -1241,7 +1244,11 @@ async def main_loop(peer): with self.assertRaises(GracefulDisconnect): await test() + # check that neither party broadcast a closing tx (as it was not even signed) + self.assertEqual(0, q1.qsize()) + self.assertEqual(0, q2.qsize()) + # -- new scenario: # bob sends the same upfront_shutdown_script has he announced alice_channel.config[HTLCOwner.REMOTE].upfront_shutdown_script = bob_uss bob_channel.config[HTLCOwner.LOCAL].upfront_shutdown_script = bob_uss @@ -1271,6 +1278,12 @@ async def main_loop(peer): with self.assertRaises(asyncio.CancelledError): await test() + # check if p1 has broadcast the closing tx, and if it pays to Bob's uss + self.assertEqual(1, q1.qsize()) + closing_tx = q1.get_nowait() # type: Transaction + self.assertEqual(2, len(closing_tx.outputs())) + self.assertEqual(1, len(closing_tx.get_output_idxs_from_address(bob_uss_addr))) + async def test_channel_usage_after_closing(self): alice_channel, bob_channel = create_test_channels() p1, p2, w1, w2, q1, q2 = self.prepare_peers(alice_channel, bob_channel)