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

@@ -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
@@ -129,6 +130,7 @@ class KeepKeyClientBase(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")
@@ -143,6 +145,7 @@ class KeepKeyClientBase(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:
@@ -153,6 +156,7 @@ class KeepKeyClientBase(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 trezorlib.'''
self.transport.write(self.proto.Cancel())
@@ -160,6 +164,7 @@ class KeepKeyClientBase(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
@@ -171,6 +176,7 @@ class KeepKeyClientBase(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")
@@ -179,14 +185,17 @@ class KeepKeyClientBase(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")
@@ -196,6 +205,7 @@ class KeepKeyClientBase(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.'''
@@ -207,10 +217,12 @@ class KeepKeyClientBase(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(KeepKeyClientBase, 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")

View File

@@ -9,7 +9,7 @@ from electrum import constants
from electrum.i18n import _
from electrum.transaction import Transaction, PartialTransaction, PartialTxInput, PartialTxOutput
from electrum.keystore import Hardware_KeyStore
from electrum.plugin import Device
from electrum.plugin import Device, runs_in_hwd_thread
from electrum.base_wizard import ScriptTypeNotSupported
from ..hw_wallet import HW_PluginBase
@@ -37,6 +37,7 @@ class KeepKey_KeyStore(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
@@ -44,6 +45,7 @@ class KeepKey_KeyStore(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
@@ -95,6 +97,7 @@ class KeepKeyPlugin(HW_PluginBase):
except ImportError:
self.libraries_available = False
@runs_in_hwd_thread
def enumerate(self):
from keepkeylib.transport_webusb import WebUsbTransport
results = []
@@ -112,16 +115,19 @@ class KeepKeyPlugin(HW_PluginBase):
def _dev_to_str(dev: "usb1.USBDevice") -> str:
return ":".join(str(x) for x in ["%03i" % (dev.getBusNumber(),)] + dev.getPortNumberList())
@runs_in_hwd_thread
def hid_transport(self, pair):
from keepkeylib.transport_hid import HidTransport
return HidTransport(pair)
@runs_in_hwd_thread
def webusb_transport(self, device):
from keepkeylib.transport_webusb import WebUsbTransport
for dev in WebUsbTransport.enumerate():
if device.path == self._dev_to_str(dev):
return WebUsbTransport(dev)
@runs_in_hwd_thread
def _try_hid(self, device):
self.logger.info("Trying to connect over USB...")
if device.interface_number == 1:
@@ -137,6 +143,7 @@ class KeepKeyPlugin(HW_PluginBase):
self.logger.info(f"cannot connect at {device.path} {e}")
return None
@runs_in_hwd_thread
def _try_webusb(self, device):
self.logger.info("Trying to connect over WebUSB...")
try:
@@ -145,6 +152,7 @@ class KeepKeyPlugin(HW_PluginBase):
self.logger.info(f"cannot connect at {device.path} {e}")
return None
@runs_in_hwd_thread
def create_client(self, device, handler):
if device.product_key[1] == 2:
transport = self._try_webusb(device)
@@ -179,6 +187,7 @@ class KeepKeyPlugin(HW_PluginBase):
return client
@runs_in_hwd_thread
def get_client(self, keystore, force_pair=True, *,
devices=None, allow_user_interaction=True) -> Optional['KeepKeyClient']:
client = super().get_client(keystore, force_pair,
@@ -236,6 +245,7 @@ class KeepKeyPlugin(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
@@ -315,6 +325,7 @@ class KeepKeyPlugin(HW_PluginBase):
return self.types.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)
@@ -325,6 +336,7 @@ class KeepKeyPlugin(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()