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)