1
0

hardware devices: run all device communication on dedicated thread (#6561)

hidapi/libusb etc are not thread-safe.

related: #6554
This commit is contained in:
ghost43
2020-09-08 15:52:53 +00:00
committed by GitHub
parent 53a5a21ee8
commit 21c3572600
12 changed files with 195 additions and 97 deletions

View File

@@ -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)

View File

@@ -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()