hardware devices: run all device communication on dedicated thread (#6561)
hidapi/libusb etc are not thread-safe. related: #6554
This commit is contained in:
@@ -8,6 +8,7 @@ from electrum.util import UserCancelled
|
||||
from electrum.keystore import bip39_normalize_passphrase
|
||||
from electrum.bip32 import BIP32Node, convert_bip32_path_to_list_of_uint32
|
||||
from electrum.logging import Logger
|
||||
from electrum.plugin import runs_in_hwd_thread
|
||||
from electrum.plugins.hw_wallet.plugin import HardwareClientBase, HardwareHandlerBase
|
||||
|
||||
|
||||
@@ -131,6 +132,7 @@ class SafeTClientBase(HardwareClientBase, GuiMixin, Logger):
|
||||
def is_pairable(self):
|
||||
return not self.features.bootloader_mode
|
||||
|
||||
@runs_in_hwd_thread
|
||||
def has_usable_connection_with_device(self):
|
||||
try:
|
||||
res = self.ping("electrum pinging device")
|
||||
@@ -145,6 +147,7 @@ class SafeTClientBase(HardwareClientBase, GuiMixin, 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:
|
||||
@@ -155,6 +158,7 @@ class SafeTClientBase(HardwareClientBase, GuiMixin, Logger):
|
||||
def expand_path(n):
|
||||
return convert_bip32_path_to_list_of_uint32(n)
|
||||
|
||||
@runs_in_hwd_thread
|
||||
def cancel(self):
|
||||
'''Provided here as in keepkeylib but not safetlib.'''
|
||||
self.transport.write(self.proto.Cancel())
|
||||
@@ -162,6 +166,7 @@ class SafeTClientBase(HardwareClientBase, GuiMixin, Logger):
|
||||
def i4b(self, x):
|
||||
return pack('>I', x)
|
||||
|
||||
@runs_in_hwd_thread
|
||||
def get_xpub(self, bip32_path, xtype):
|
||||
address_n = self.expand_path(bip32_path)
|
||||
creating = False
|
||||
@@ -173,6 +178,7 @@ class SafeTClientBase(HardwareClientBase, GuiMixin, 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:
|
||||
self.msg = _("Confirm on your {} device to disable passphrases")
|
||||
@@ -181,14 +187,17 @@ class SafeTClientBase(HardwareClientBase, GuiMixin, Logger):
|
||||
enabled = not self.features.passphrase_protection
|
||||
self.apply_settings(use_passphrase=enabled)
|
||||
|
||||
@runs_in_hwd_thread
|
||||
def change_label(self, label):
|
||||
self.msg = _("Confirm the new label on your {} device")
|
||||
self.apply_settings(label=label)
|
||||
|
||||
@runs_in_hwd_thread
|
||||
def change_homescreen(self, homescreen):
|
||||
self.msg = _("Confirm on your {} device to change your home screen")
|
||||
self.apply_settings(homescreen=homescreen)
|
||||
|
||||
@runs_in_hwd_thread
|
||||
def set_pin(self, remove):
|
||||
if remove:
|
||||
self.msg = _("Confirm on your {} device to disable PIN protection")
|
||||
@@ -198,6 +207,7 @@ class SafeTClientBase(HardwareClientBase, GuiMixin, Logger):
|
||||
self.msg = _("Confirm on your {} device to set a PIN")
|
||||
self.change_pin(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.'''
|
||||
@@ -209,10 +219,12 @@ class SafeTClientBase(HardwareClientBase, GuiMixin, 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 get_public_node(self, address_n, creating):
|
||||
self.creating_wallet = creating
|
||||
return super(SafeTClientBase, self).get_public_node(address_n)
|
||||
|
||||
@runs_in_hwd_thread
|
||||
def close(self):
|
||||
'''Called when Our wallet was closed or the device removed.'''
|
||||
self.logger.info("closing client")
|
||||
|
||||
@@ -7,7 +7,7 @@ from electrum.util import bfh, bh2u, versiontuple, UserCancelled, UserFacingExce
|
||||
from electrum.bip32 import BIP32Node
|
||||
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
|
||||
@@ -35,6 +35,7 @@ class SafeTKeyStore(Hardware_KeyStore):
|
||||
def decrypt_message(self, sequence, message, password):
|
||||
raise UserFacingException(_('Encryption and decryption are not implemented by {}').format(self.device))
|
||||
|
||||
@runs_in_hwd_thread
|
||||
def sign_message(self, sequence, message, password):
|
||||
client = self.get_client()
|
||||
address_path = self.get_derivation_prefix() + "/%d/%d"%sequence
|
||||
@@ -42,6 +43,7 @@ class SafeTKeyStore(Hardware_KeyStore):
|
||||
msg_sig = client.sign_message(self.plugin.get_coin_name(), address_n, message)
|
||||
return msg_sig.signature
|
||||
|
||||
@runs_in_hwd_thread
|
||||
def sign_transaction(self, tx, password):
|
||||
if tx.is_complete():
|
||||
return
|
||||
@@ -96,6 +98,7 @@ class SafeTPlugin(HW_PluginBase):
|
||||
except AttributeError:
|
||||
return 'unknown'
|
||||
|
||||
@runs_in_hwd_thread
|
||||
def enumerate(self):
|
||||
devices = self.transport_handler.enumerate_devices()
|
||||
return [Device(path=d.get_path(),
|
||||
@@ -106,6 +109,7 @@ class SafeTPlugin(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}")
|
||||
@@ -141,6 +145,7 @@ class SafeTPlugin(HW_PluginBase):
|
||||
|
||||
return client
|
||||
|
||||
@runs_in_hwd_thread
|
||||
def get_client(self, keystore, force_pair=True, *,
|
||||
devices=None, allow_user_interaction=True) -> Optional['SafeTClient']:
|
||||
client = super().get_client(keystore, force_pair,
|
||||
@@ -198,6 +203,7 @@ class SafeTPlugin(HW_PluginBase):
|
||||
finally:
|
||||
wizard.loop.exit(exit_code)
|
||||
|
||||
@runs_in_hwd_thread
|
||||
def _initialize_device(self, settings, method, device_id, wizard, handler):
|
||||
item, label, pin_protection, passphrase_protection = settings
|
||||
|
||||
@@ -289,6 +295,7 @@ class SafeTPlugin(HW_PluginBase):
|
||||
return self.types.OutputScriptType.PAYTOMULTISIG
|
||||
raise ValueError('unexpected txin type: {}'.format(electrum_txin_type))
|
||||
|
||||
@runs_in_hwd_thread
|
||||
def sign_transaction(self, keystore, tx: PartialTransaction, prev_tx):
|
||||
self.prev_tx = prev_tx
|
||||
client = self.get_client(keystore)
|
||||
@@ -299,6 +306,7 @@ class SafeTPlugin(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