hardware devices: run all device communication on dedicated thread (#6561)
hidapi/libusb etc are not thread-safe. related: #6554
This commit is contained in:
@@ -7,6 +7,7 @@ from electrum.util import UserCancelled, UserFacingException
|
||||
from electrum.keystore import bip39_normalize_passphrase
|
||||
from electrum.bip32 import BIP32Node, convert_bip32_path_to_list_of_uint32 as parse_path
|
||||
from electrum.logging import Logger
|
||||
from electrum.plugin import runs_in_hwd_thread
|
||||
from electrum.plugins.hw_wallet.plugin import OutdatedHwFirmwareException, HardwareClientBase
|
||||
|
||||
from trezorlib.client import TrezorClient, PASSPHRASE_ON_DEVICE
|
||||
@@ -107,6 +108,7 @@ class TrezorClientBase(HardwareClientBase, Logger):
|
||||
def is_pairable(self):
|
||||
return not self.features.bootloader_mode
|
||||
|
||||
@runs_in_hwd_thread
|
||||
def has_usable_connection_with_device(self):
|
||||
if self.in_flow:
|
||||
return True
|
||||
@@ -123,6 +125,7 @@ class TrezorClientBase(HardwareClientBase, Logger):
|
||||
def prevent_timeouts(self):
|
||||
self.last_operation = float('inf')
|
||||
|
||||
@runs_in_hwd_thread
|
||||
def timeout(self, cutoff):
|
||||
'''Time out the client if the last operation was before cutoff.'''
|
||||
if self.last_operation < cutoff:
|
||||
@@ -132,6 +135,7 @@ class TrezorClientBase(HardwareClientBase, Logger):
|
||||
def i4b(self, x):
|
||||
return pack('>I', x)
|
||||
|
||||
@runs_in_hwd_thread
|
||||
def get_xpub(self, bip32_path, xtype, creating=False):
|
||||
address_n = parse_path(bip32_path)
|
||||
with self.run_flow(creating_wallet=creating):
|
||||
@@ -143,6 +147,7 @@ class TrezorClientBase(HardwareClientBase, Logger):
|
||||
fingerprint=self.i4b(node.fingerprint),
|
||||
child_number=self.i4b(node.child_num)).to_xpub()
|
||||
|
||||
@runs_in_hwd_thread
|
||||
def toggle_passphrase(self):
|
||||
if self.features.passphrase_protection:
|
||||
msg = _("Confirm on your {} device to disable passphrases")
|
||||
@@ -152,14 +157,17 @@ class TrezorClientBase(HardwareClientBase, Logger):
|
||||
with self.run_flow(msg):
|
||||
trezorlib.device.apply_settings(self.client, use_passphrase=enabled)
|
||||
|
||||
@runs_in_hwd_thread
|
||||
def change_label(self, label):
|
||||
with self.run_flow(_("Confirm the new label on your {} device")):
|
||||
trezorlib.device.apply_settings(self.client, label=label)
|
||||
|
||||
@runs_in_hwd_thread
|
||||
def change_homescreen(self, homescreen):
|
||||
with self.run_flow(_("Confirm on your {} device to change your home screen")):
|
||||
trezorlib.device.apply_settings(self.client, homescreen=homescreen)
|
||||
|
||||
@runs_in_hwd_thread
|
||||
def set_pin(self, remove):
|
||||
if remove:
|
||||
msg = _("Confirm on your {} device to disable PIN protection")
|
||||
@@ -170,6 +178,7 @@ class TrezorClientBase(HardwareClientBase, Logger):
|
||||
with self.run_flow(msg):
|
||||
trezorlib.device.change_pin(self.client, remove)
|
||||
|
||||
@runs_in_hwd_thread
|
||||
def clear_session(self):
|
||||
'''Clear the session to force pin (and passphrase if enabled)
|
||||
re-entry. Does not leak exceptions.'''
|
||||
@@ -181,11 +190,13 @@ class TrezorClientBase(HardwareClientBase, Logger):
|
||||
# If the device was removed it has the same effect...
|
||||
self.logger.info(f"clear_session: ignoring error {e}")
|
||||
|
||||
@runs_in_hwd_thread
|
||||
def close(self):
|
||||
'''Called when Our wallet was closed or the device removed.'''
|
||||
self.logger.info("closing client")
|
||||
self.clear_session()
|
||||
|
||||
@runs_in_hwd_thread
|
||||
def is_uptodate(self):
|
||||
if self.client.is_outdated():
|
||||
return False
|
||||
@@ -203,6 +214,7 @@ class TrezorClientBase(HardwareClientBase, Logger):
|
||||
return "Trezor T"
|
||||
return None
|
||||
|
||||
@runs_in_hwd_thread
|
||||
def show_address(self, address_str, script_type, multisig=None):
|
||||
coin_name = self.plugin.get_coin_name()
|
||||
address_n = parse_path(address_str)
|
||||
@@ -215,6 +227,7 @@ class TrezorClientBase(HardwareClientBase, Logger):
|
||||
script_type=script_type,
|
||||
multisig=multisig)
|
||||
|
||||
@runs_in_hwd_thread
|
||||
def sign_message(self, address_str, message):
|
||||
coin_name = self.plugin.get_coin_name()
|
||||
address_n = parse_path(address_str)
|
||||
@@ -225,6 +238,7 @@ class TrezorClientBase(HardwareClientBase, Logger):
|
||||
address_n,
|
||||
message)
|
||||
|
||||
@runs_in_hwd_thread
|
||||
def recover_device(self, recovery_type, *args, **kwargs):
|
||||
input_callback = self.mnemonic_callback(recovery_type)
|
||||
with self.run_flow():
|
||||
@@ -237,14 +251,17 @@ class TrezorClientBase(HardwareClientBase, Logger):
|
||||
|
||||
# ========= Unmodified trezorlib methods =========
|
||||
|
||||
@runs_in_hwd_thread
|
||||
def sign_tx(self, *args, **kwargs):
|
||||
with self.run_flow():
|
||||
return trezorlib.btc.sign_tx(self.client, *args, **kwargs)
|
||||
|
||||
@runs_in_hwd_thread
|
||||
def reset_device(self, *args, **kwargs):
|
||||
with self.run_flow():
|
||||
return trezorlib.device.reset(self.client, *args, **kwargs)
|
||||
|
||||
@runs_in_hwd_thread
|
||||
def wipe_device(self, *args, **kwargs):
|
||||
with self.run_flow():
|
||||
return trezorlib.device.wipe(self.client, *args, **kwargs)
|
||||
|
||||
@@ -6,7 +6,7 @@ from electrum.util import bfh, bh2u, versiontuple, UserCancelled, UserFacingExce
|
||||
from electrum.bip32 import BIP32Node, convert_bip32_path_to_list_of_uint32 as parse_path
|
||||
from electrum import constants
|
||||
from electrum.i18n import _
|
||||
from electrum.plugin import Device
|
||||
from electrum.plugin import Device, runs_in_hwd_thread
|
||||
from electrum.transaction import Transaction, PartialTransaction, PartialTxInput, PartialTxOutput
|
||||
from electrum.keystore import Hardware_KeyStore
|
||||
from electrum.base_wizard import ScriptTypeNotSupported, HWD_SETUP_NEW_WALLET
|
||||
@@ -143,6 +143,7 @@ class TrezorPlugin(HW_PluginBase):
|
||||
else:
|
||||
raise LibraryFoundButUnusable(library_version=version)
|
||||
|
||||
@runs_in_hwd_thread
|
||||
def is_bridge_available(self) -> bool:
|
||||
# Testing whether the Bridge is available can take several seconds
|
||||
# (when it is not), as it is slow to timeout, hence we cache it.
|
||||
@@ -157,6 +158,7 @@ class TrezorPlugin(HW_PluginBase):
|
||||
self._is_bridge_available = True
|
||||
return self._is_bridge_available
|
||||
|
||||
@runs_in_hwd_thread
|
||||
def enumerate(self):
|
||||
# If there is a bridge, prefer that.
|
||||
# On Windows, the bridge runs as Admin (and Electrum usually does not),
|
||||
@@ -174,6 +176,7 @@ class TrezorPlugin(HW_PluginBase):
|
||||
transport_ui_string=d.get_path())
|
||||
for d in devices]
|
||||
|
||||
@runs_in_hwd_thread
|
||||
def create_client(self, device, handler):
|
||||
try:
|
||||
self.logger.info(f"connecting to device at {device.path}")
|
||||
@@ -190,6 +193,7 @@ class TrezorPlugin(HW_PluginBase):
|
||||
# note that this call can still raise!
|
||||
return TrezorClientBase(transport, handler, self)
|
||||
|
||||
@runs_in_hwd_thread
|
||||
def get_client(self, keystore, force_pair=True, *,
|
||||
devices=None, allow_user_interaction=True) -> Optional['TrezorClientBase']:
|
||||
client = super().get_client(keystore, force_pair,
|
||||
@@ -238,6 +242,7 @@ class TrezorPlugin(HW_PluginBase):
|
||||
finally:
|
||||
wizard.loop.exit(exit_code)
|
||||
|
||||
@runs_in_hwd_thread
|
||||
def _initialize_device(self, settings: TrezorInitSettings, method, device_id, wizard, handler):
|
||||
if method == TIM_RECOVER and settings.recovery_type == RecoveryDeviceType.ScrambledWords:
|
||||
handler.show_error(_(
|
||||
@@ -333,6 +338,7 @@ class TrezorPlugin(HW_PluginBase):
|
||||
return OutputScriptType.PAYTOMULTISIG
|
||||
raise ValueError('unexpected txin type: {}'.format(electrum_txin_type))
|
||||
|
||||
@runs_in_hwd_thread
|
||||
def sign_transaction(self, keystore, tx: PartialTransaction, prev_tx):
|
||||
prev_tx = { bfh(txhash): self.electrum_tx_to_txtype(tx) for txhash, tx in prev_tx.items() }
|
||||
client = self.get_client(keystore)
|
||||
@@ -343,6 +349,7 @@ class TrezorPlugin(HW_PluginBase):
|
||||
signatures = [(bh2u(x) + '01') for x in signatures]
|
||||
tx.update_signatures(signatures)
|
||||
|
||||
@runs_in_hwd_thread
|
||||
def show_address(self, wallet, address, keystore=None):
|
||||
if keystore is None:
|
||||
keystore = wallet.get_keystore()
|
||||
|
||||
Reference in New Issue
Block a user