commit 21c35726009295412b0d5d51e8dff6c2ac064842
parent 53a5a21ee89b791c351ad39a47e69837ed347af8
Author: ghost43 <somber.night@protonmail.com>
Date: Tue, 8 Sep 2020 15:52:53 +0000
hardware devices: run all device communication on dedicated thread (#6561)
hidapi/libusb etc are not thread-safe.
related: #6554
Diffstat:
12 files changed, 195 insertions(+), 97 deletions(-)
diff --git a/electrum/plugin.py b/electrum/plugin.py
@@ -29,9 +29,10 @@ import time
import threading
import sys
from typing import (NamedTuple, Any, Union, TYPE_CHECKING, Optional, Tuple,
- Dict, Iterable, List, Sequence)
+ Dict, Iterable, List, Sequence, Callable, TypeVar)
import concurrent
from concurrent import futures
+from functools import wraps, partial
from .i18n import _
from .util import (profiler, DaemonThread, UserCancelled, ThreadJob, UserFacingException)
@@ -334,11 +335,37 @@ PLACEHOLDER_HW_CLIENT_LABELS = {None, "", " "}
# https://github.com/signal11/hidapi/pull/414#issuecomment-445164238
# It is not entirely clear to me, exactly what is safe and what isn't, when
# using multiple threads...
-# For now, we use a dedicated thread to enumerate devices (_hid_executor),
-# and we synchronize all device opens/closes/enumeration (_hid_lock).
-# FIXME there are still probably threading issues with how we use hidapi...
-_hid_executor = None # type: Optional[concurrent.futures.Executor]
-_hid_lock = threading.Lock()
+# Hence, we use a single thread for all device communications, including
+# enumeration. Everything that uses hidapi, libusb, etc, MUST run on
+# the following thread:
+_hwd_comms_executor = concurrent.futures.ThreadPoolExecutor(
+ max_workers=1,
+ thread_name_prefix='hwd_comms_thread'
+)
+
+
+T = TypeVar('T')
+
+
+def run_in_hwd_thread(func: Callable[[], T]) -> T:
+ if threading.current_thread().name.startswith("hwd_comms_thread"):
+ return func()
+ else:
+ fut = _hwd_comms_executor.submit(func)
+ return fut.result()
+ #except (concurrent.futures.CancelledError, concurrent.futures.TimeoutError) as e:
+
+
+def runs_in_hwd_thread(func):
+ @wraps(func)
+ def wrapper(*args, **kwargs):
+ return run_in_hwd_thread(partial(func, *args, **kwargs))
+ return wrapper
+
+
+def assert_runs_in_hwd_thread():
+ if not threading.current_thread().name.startswith("hwd_comms_thread"):
+ raise Exception("must only be called from HWD communication thread")
class DeviceMgr(ThreadJob):
@@ -384,24 +411,11 @@ class DeviceMgr(ThreadJob):
self._recognised_hardware = {} # type: Dict[Tuple[int, int], HW_PluginBase]
# Custom enumerate functions for devices we don't know about.
self._enumerate_func = set() # Needs self.lock.
- # locks: if you need to take multiple ones, acquire them in the order they are defined here!
- self._scan_lock = threading.RLock()
+
self.lock = threading.RLock()
- self.hid_lock = _hid_lock
self.config = config
- global _hid_executor
- if _hid_executor is None:
- _hid_executor = concurrent.futures.ThreadPoolExecutor(max_workers=1,
- thread_name_prefix='hid_enumerate_thread')
-
- def with_scan_lock(func):
- def func_wrapper(self: 'DeviceMgr', *args, **kwargs):
- with self._scan_lock:
- return func(self, *args, **kwargs)
- return func_wrapper
-
def thread_jobs(self):
# Thread job to handle device timeouts
return [self]
@@ -423,6 +437,7 @@ class DeviceMgr(ThreadJob):
with self.lock:
self._enumerate_func.add(func)
+ @runs_in_hwd_thread
def create_client(self, device: 'Device', handler: Optional['HardwareHandlerBase'],
plugin: 'HW_PluginBase') -> Optional['HardwareClientBase']:
# Get from cache first
@@ -452,7 +467,7 @@ class DeviceMgr(ThreadJob):
if xpub not in self.xpub_ids:
return
_id = self.xpub_ids.pop(xpub)
- self._close_client(_id)
+ self._close_client(_id)
def unpair_id(self, id_):
xpub = self.xpub_by_id(id_)
@@ -462,8 +477,9 @@ class DeviceMgr(ThreadJob):
self._close_client(id_)
def _close_client(self, id_):
- client = self._client_by_id(id_)
- self.clients.pop(client, None)
+ with self.lock:
+ client = self._client_by_id(id_)
+ self.clients.pop(client, None)
if client:
client.close()
@@ -486,7 +502,7 @@ class DeviceMgr(ThreadJob):
self.scan_devices()
return self._client_by_id(id_)
- @with_scan_lock
+ @runs_in_hwd_thread
def client_for_keystore(self, plugin: 'HW_PluginBase', handler: Optional['HardwareHandlerBase'],
keystore: 'Hardware_KeyStore',
force_pair: bool, *,
@@ -655,25 +671,15 @@ class DeviceMgr(ThreadJob):
# note: updated label/soft_device_id will be saved after pairing succeeds
return info
- @with_scan_lock
+ @runs_in_hwd_thread
def _scan_devices_with_hid(self) -> List['Device']:
try:
import hid
except ImportError:
return []
- def hid_enumerate():
- with self.hid_lock:
- return hid.enumerate(0, 0)
-
- hid_list_fut = _hid_executor.submit(hid_enumerate)
- try:
- hid_list = hid_list_fut.result()
- except (concurrent.futures.CancelledError, concurrent.futures.TimeoutError) as e:
- return []
-
devices = []
- for d in hid_list:
+ for d in hid.enumerate(0, 0):
product_key = (d['vendor_id'], d['product_id'])
if product_key in self._recognised_hardware:
plugin = self._recognised_hardware[product_key]
@@ -681,7 +687,7 @@ class DeviceMgr(ThreadJob):
devices.append(device)
return devices
- @with_scan_lock
+ @runs_in_hwd_thread
@profiler
def scan_devices(self) -> Sequence['Device']:
self.logger.info("scanning devices...")
@@ -693,10 +699,8 @@ class DeviceMgr(ThreadJob):
with self.lock:
enumerate_funcs = list(self._enumerate_func)
for f in enumerate_funcs:
- # custom enumerate functions might use hidapi, so use hid thread to be safe
- new_devices_fut = _hid_executor.submit(f)
try:
- new_devices = new_devices_fut.result()
+ new_devices = f()
except BaseException as e:
self.logger.error('custom device enum failed. func {}, error {}'
.format(str(f), repr(e)))
diff --git a/electrum/plugins/bitbox02/bitbox02.py b/electrum/plugins/bitbox02/bitbox02.py
@@ -13,7 +13,7 @@ from electrum.wallet import Standard_Wallet, Multisig_Wallet, Deterministic_Wall
from electrum.util import bh2u, UserFacingException
from electrum.base_wizard import ScriptTypeNotSupported, BaseWizard
from electrum.logging import get_logger
-from electrum.plugin import Device, DeviceInfo
+from electrum.plugin import Device, DeviceInfo, runs_in_hwd_thread
from electrum.simple_config import SimpleConfig
from electrum.json_db import StoredDict
from electrum.storage import get_derivation_used_for_hw_device_encryption
@@ -73,18 +73,19 @@ class BitBox02Client(HardwareClientBase):
def is_initialized(self) -> bool:
return True
+ @runs_in_hwd_thread
def close(self):
- with self.device_manager().hid_lock:
- try:
- self.bitbox02_device.close()
- except:
- pass
+ try:
+ self.bitbox02_device.close()
+ except:
+ pass
def has_usable_connection_with_device(self) -> bool:
if self.bitbox_hid_info is None:
return False
return True
+ @runs_in_hwd_thread
def get_soft_device_id(self) -> Optional[str]:
if self.handler is None:
# Can't do the pairing without the handler. This happens at wallet creation time, when
@@ -94,6 +95,7 @@ class BitBox02Client(HardwareClientBase):
self.pairing_dialog()
return self.bitbox02_device.root_fingerprint().hex()
+ @runs_in_hwd_thread
def pairing_dialog(self):
def pairing_step(code: str, device_response: Callable[[], bool]) -> bool:
msg = "Please compare and confirm the pairing code on your BitBox02:\n" + code
@@ -102,8 +104,7 @@ class BitBox02Client(HardwareClientBase):
res = device_response()
except:
# Close the hid device on exception
- with self.device_manager().hid_lock:
- hid_device.close()
+ hid_device.close()
raise
finally:
self.handler.finished()
@@ -167,10 +168,8 @@ class BitBox02Client(HardwareClientBase):
return set_noise_privkey(privkey)
if self.bitbox02_device is None:
- with self.device_manager().hid_lock:
- hid_device = hid.device()
- hid_device.open_path(self.bitbox_hid_info["path"])
-
+ hid_device = hid.device()
+ hid_device.open_path(self.bitbox_hid_info["path"])
bitbox02_device = bitbox02.BitBox02(
transport=u2fhid.U2FHid(hid_device),
@@ -197,6 +196,7 @@ class BitBox02Client(HardwareClientBase):
return bitbox02.btc.TBTC
return bitbox02.btc.BTC
+ @runs_in_hwd_thread
def get_password_for_storage_encryption(self) -> str:
derivation = get_derivation_used_for_hw_device_encryption()
derivation_list = bip32.convert_bip32_path_to_list_of_uint32(derivation)
@@ -204,6 +204,7 @@ class BitBox02Client(HardwareClientBase):
node = bip32.BIP32Node.from_xkey(xpub, net = constants.BitcoinMainnet()).subkey_at_public_derivation(())
return node.eckey.get_public_key_bytes(compressed=True).hex()
+ @runs_in_hwd_thread
def get_xpub(self, bip32_path: str, xtype: str, *, display: bool = False) -> str:
if self.bitbox02_device is None:
self.pairing_dialog()
@@ -244,6 +245,7 @@ class BitBox02Client(HardwareClientBase):
display=display,
)
+ @runs_in_hwd_thread
def label(self) -> str:
if self.handler is None:
# Can't do the pairing without the handler. This happens at wallet creation time, when
@@ -258,6 +260,7 @@ class BitBox02Client(HardwareClientBase):
self.bitbox02_device.root_fingerprint().hex(),
)
+ @runs_in_hwd_thread
def request_root_fingerprint_from_device(self) -> str:
if self.bitbox02_device is None:
raise Exception(
@@ -271,6 +274,7 @@ class BitBox02Client(HardwareClientBase):
return False
return True
+ @runs_in_hwd_thread
def btc_multisig_config(
self, coin, bip32_path: List[int], wallet: Multisig_Wallet
):
@@ -316,6 +320,7 @@ class BitBox02Client(HardwareClientBase):
raise UserFacingException("Failed to register multisig\naccount configuration on BitBox02")
return multisig_config
+ @runs_in_hwd_thread
def show_address(
self, bip32_path: str, address_type: str, wallet: Deterministic_Wallet
) -> str:
@@ -357,6 +362,7 @@ class BitBox02Client(HardwareClientBase):
display=True,
)
+ @runs_in_hwd_thread
def sign_transaction(
self,
keystore: Hardware_KeyStore,
@@ -553,6 +559,7 @@ class BitBox02_KeyStore(Hardware_KeyStore):
).format(self.device)
)
+ @runs_in_hwd_thread
def sign_transaction(self, tx: PartialTransaction, password: str):
if tx.is_complete():
return
@@ -572,6 +579,7 @@ class BitBox02_KeyStore(Hardware_KeyStore):
self.give_error(e, True)
return
+ @runs_in_hwd_thread
def show_address(
self, sequence: Tuple[int, int], txin_type: str, wallet: Deterministic_Wallet
):
@@ -616,6 +624,7 @@ class BitBox02Plugin(HW_PluginBase):
raise ImportError()
# handler is a BitBox02_Handler
+ @runs_in_hwd_thread
def create_client(self, device: Device, handler: Any) -> BitBox02Client:
if not handler:
self.handler = handler
@@ -645,6 +654,7 @@ class BitBox02Plugin(HW_PluginBase):
assert client.bitbox02_device is not None
return client.get_xpub(derivation, xtype)
+ @runs_in_hwd_thread
def show_address(
self,
wallet: Deterministic_Wallet,
@@ -660,6 +670,7 @@ class BitBox02Plugin(HW_PluginBase):
sequence = wallet.get_address_index(address)
keystore.show_address(sequence, txin_type, wallet)
+ @runs_in_hwd_thread
def show_xpub(self, keystore: BitBox02_KeyStore):
client = keystore.get_client()
assert isinstance(client, BitBox02Client)
diff --git a/electrum/plugins/coldcard/coldcard.py b/electrum/plugins/coldcard/coldcard.py
@@ -10,7 +10,7 @@ import struct
from electrum import bip32
from electrum.bip32 import BIP32Node, InvalidMasterKeyVersionBytes
from electrum.i18n import _
-from electrum.plugin import Device, hook
+from electrum.plugin import Device, hook, runs_in_hwd_thread
from electrum.keystore import Hardware_KeyStore, KeyStoreWithMPK
from electrum.transaction import PartialTransaction
from electrum.wallet import Standard_Wallet, Multisig_Wallet, Abstract_Wallet
@@ -72,9 +72,8 @@ class CKCCClient(HardwareClientBase):
self.dev = ElectrumColdcardDevice(dev_path, encrypt=True)
else:
# open the real HID device
- with self.device_manager().hid_lock:
- hd = hid.device(path=dev_path)
- hd.open_path(dev_path)
+ hd = hid.device(path=dev_path)
+ hd.open_path(dev_path)
self.dev = ElectrumColdcardDevice(dev=hd, encrypt=True)
@@ -85,6 +84,7 @@ class CKCCClient(HardwareClientBase):
return '<CKCCClient: xfp=%s label=%r>' % (xfp2str(self.dev.master_fingerprint),
self.label())
+ @runs_in_hwd_thread
def verify_connection(self, expected_xfp: int, expected_xpub=None):
ex = (expected_xfp, expected_xpub)
@@ -121,14 +121,10 @@ class CKCCClient(HardwareClientBase):
# can't do anything w/ devices that aren't setup (this code not normally reachable)
return bool(self.dev.master_xpub)
- def timeout(self, cutoff):
- # nothing to do?
- pass
-
+ @runs_in_hwd_thread
def close(self):
# close the HID device (so can be reused)
- with self.device_manager().hid_lock:
- self.dev.close()
+ self.dev.close()
self.dev = None
def is_initialized(self):
@@ -160,6 +156,7 @@ class CKCCClient(HardwareClientBase):
return LabelStr(lab, self.dev.master_fingerprint, self.dev.master_xpub)
+ @runs_in_hwd_thread
def has_usable_connection_with_device(self):
# Do end-to-end ping test
try:
@@ -168,6 +165,7 @@ class CKCCClient(HardwareClientBase):
except:
return False
+ @runs_in_hwd_thread
def get_xpub(self, bip32_path, xtype):
assert xtype in ColdcardPlugin.SUPPORTED_XTYPES
_logger.info('Derive xtype = %r' % xtype)
@@ -183,6 +181,7 @@ class CKCCClient(HardwareClientBase):
xpub = node._replace(xtype=xtype).to_xpub()
return xpub
+ @runs_in_hwd_thread
def ping_check(self):
# check connection is working
assert self.dev.session_key, 'not encrypted?'
@@ -193,26 +192,32 @@ class CKCCClient(HardwareClientBase):
except:
raise RuntimeError("Communication trouble with Coldcard")
+ @runs_in_hwd_thread
def show_address(self, path, addr_fmt):
# prompt user w/ address, also returns it immediately.
return self.dev.send_recv(CCProtocolPacker.show_address(path, addr_fmt), timeout=None)
+ @runs_in_hwd_thread
def show_p2sh_address(self, *args, **kws):
# prompt user w/ p2sh address, also returns it immediately.
return self.dev.send_recv(CCProtocolPacker.show_p2sh_address(*args, **kws), timeout=None)
+ @runs_in_hwd_thread
def get_version(self):
# gives list of strings
return self.dev.send_recv(CCProtocolPacker.version(), timeout=1000).split('\n')
+ @runs_in_hwd_thread
def sign_message_start(self, path, msg):
# this starts the UX experience.
self.dev.send_recv(CCProtocolPacker.sign_message(msg, path), timeout=None)
+ @runs_in_hwd_thread
def sign_message_poll(self):
# poll device... if user has approved, will get tuple: (addr, sig) else None
return self.dev.send_recv(CCProtocolPacker.get_signed_msg(), timeout=None)
+ @runs_in_hwd_thread
def sign_transaction_start(self, raw_psbt: bytes, *, finalize: bool = False):
# Multiple steps to sign:
# - upload binary
@@ -228,10 +233,12 @@ class CKCCClient(HardwareClientBase):
if resp != None:
raise ValueError(resp)
+ @runs_in_hwd_thread
def sign_transaction_poll(self):
# poll device... if user has approved, will get tuple: (legnth, checksum) else None
return self.dev.send_recv(CCProtocolPacker.get_signed_txn(), timeout=None)
+ @runs_in_hwd_thread
def download_file(self, length, checksum, file_number=1):
# get a file
return self.dev.download_file(length, checksum, file_number=file_number)
@@ -317,7 +324,6 @@ class Coldcard_KeyStore(Hardware_KeyStore):
% MSG_SIGNING_MAX_LENGTH)
return b''
- client = self.get_client()
path = self.get_derivation_prefix() + ("/%d/%d" % sequence)
try:
cl = self.get_client()
@@ -508,6 +514,7 @@ class ColdcardPlugin(HW_PluginBase):
return []
+ @runs_in_hwd_thread
def create_client(self, device, handler):
if handler:
self.handler = handler
@@ -539,6 +546,7 @@ class ColdcardPlugin(HW_PluginBase):
xpub = client.get_xpub(derivation, xtype)
return xpub
+ @runs_in_hwd_thread
def get_client(self, keystore, force_pair=True, *,
devices=None, allow_user_interaction=True) -> Optional['CKCCClient']:
# Acquire a connection to the hardware device (via USB)
diff --git a/electrum/plugins/digitalbitbox/digitalbitbox.py b/electrum/plugins/digitalbitbox/digitalbitbox.py
@@ -30,6 +30,7 @@ from electrum.util import to_string, UserCancelled, UserFacingException, bfh
from electrum.base_wizard import ScriptTypeNotSupported, HWD_SETUP_NEW_WALLET
from electrum.network import Network
from electrum.logging import get_logger
+from electrum.plugin import runs_in_hwd_thread, run_in_hwd_thread
from ..hw_wallet import HW_PluginBase, HardwareClientBase
@@ -74,21 +75,16 @@ class DigitalBitbox_Client(HardwareClientBase):
self.setupRunning = False
self.usbReportSize = 64 # firmware > v2.0.0
-
+ @runs_in_hwd_thread
def close(self):
if self.opened:
- with self.device_manager().hid_lock:
- try:
- self.dbb_hid.close()
- except:
- pass
+ try:
+ self.dbb_hid.close()
+ except:
+ pass
self.opened = False
- def timeout(self, cutoff):
- pass
-
-
def is_pairable(self):
return True
@@ -111,7 +107,6 @@ class DigitalBitbox_Client(HardwareClientBase):
if self.check_device_dialog():
return self.hid_send_encrypt(('{"xpub": "%s"}' % bip32_path).encode('utf8'))
-
def get_xpub(self, bip32_path, xtype):
assert xtype in self.plugin.SUPPORTED_XTYPES
reply = self._get_xpub(bip32_path)
@@ -171,9 +166,9 @@ class DigitalBitbox_Client(HardwareClientBase):
self.password = password.encode('utf8')
return True
-
def check_device_dialog(self):
- match = re.search(r'v([0-9])+\.[0-9]+\.[0-9]+', self.dbb_hid.get_serial_number_string())
+ match = re.search(r'v([0-9])+\.[0-9]+\.[0-9]+',
+ run_in_hwd_thread(self.dbb_hid.get_serial_number_string))
if match is None:
raise Exception("error detecting firmware version")
major_version = int(match.group(1))
@@ -350,7 +345,7 @@ class DigitalBitbox_Client(HardwareClientBase):
raise UserFacingException(hid_reply['error']['message'])
return True
-
+ @runs_in_hwd_thread
def hid_send_frame(self, data):
HWW_CID = 0xFF000000
HWW_CMD = 0x80 + 0x40 + 0x01
@@ -370,7 +365,7 @@ class DigitalBitbox_Client(HardwareClientBase):
seq += 1
idx += len(write)
-
+ @runs_in_hwd_thread
def hid_read_frame(self):
# INIT response
read = bytearray(self.dbb_hid.read(self.usbReportSize))
@@ -386,7 +381,7 @@ class DigitalBitbox_Client(HardwareClientBase):
idx += len(read) - 5
return data
-
+ @runs_in_hwd_thread
def hid_send_plain(self, msg):
reply = ""
try:
@@ -408,7 +403,7 @@ class DigitalBitbox_Client(HardwareClientBase):
_logger.info(f'Exception caught {repr(e)}')
return reply
-
+ @runs_in_hwd_thread
def hid_send_encrypt(self, msg):
sha256_byte_len = 32
reply = ""
@@ -680,11 +675,10 @@ class DigitalBitboxPlugin(HW_PluginBase):
self.digitalbitbox_config = self.config.get('digitalbitbox', {})
-
+ @runs_in_hwd_thread
def get_dbb_device(self, device):
- with self.device_manager().hid_lock:
- dev = hid.device()
- dev.open_path(device.path)
+ dev = hid.device()
+ dev.open_path(device.path)
return dev
diff --git a/electrum/plugins/hw_wallet/plugin.py b/electrum/plugins/hw_wallet/plugin.py
@@ -27,7 +27,8 @@
from typing import TYPE_CHECKING, Dict, List, Union, Tuple, Sequence, Optional, Type
from functools import partial
-from electrum.plugin import BasePlugin, hook, Device, DeviceMgr, DeviceInfo
+from electrum.plugin import (BasePlugin, hook, Device, DeviceMgr, DeviceInfo,
+ assert_runs_in_hwd_thread, runs_in_hwd_thread)
from electrum.i18n import _
from electrum.bitcoin import is_address, opcodes
from electrum.util import bfh, versiontuple, UserFacingException
@@ -197,6 +198,7 @@ class HardwareClientBase:
handler = None # type: Optional['HardwareHandlerBase']
def __init__(self, *, plugin: 'HW_PluginBase'):
+ assert_runs_in_hwd_thread()
self.plugin = plugin
def device_manager(self) -> 'DeviceMgr':
@@ -242,6 +244,7 @@ class HardwareClientBase:
def get_xpub(self, bip32_path: str, xtype) -> str:
raise NotImplementedError()
+ @runs_in_hwd_thread
def request_root_fingerprint_from_device(self) -> str:
# digitalbitbox (at least) does not reveal xpubs corresponding to unhardened paths
# so ask for a direct child, and read out fingerprint from that:
@@ -249,6 +252,7 @@ class HardwareClientBase:
root_fingerprint = BIP32Node.from_xkey(child_of_root_xpub).fingerprint.hex().lower()
return root_fingerprint
+ @runs_in_hwd_thread
def get_password_for_storage_encryption(self) -> str:
# note: using a different password based on hw device type is highly undesirable! see #5993
derivation = get_derivation_used_for_hw_device_encryption()
diff --git a/electrum/plugins/keepkey/clientbase.py b/electrum/plugins/keepkey/clientbase.py
@@ -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")
diff --git a/electrum/plugins/keepkey/keepkey.py b/electrum/plugins/keepkey/keepkey.py
@@ -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()
diff --git a/electrum/plugins/ledger/ledger.py b/electrum/plugins/ledger/ledger.py
@@ -16,6 +16,7 @@ from electrum.wallet import Standard_Wallet
from electrum.util import bfh, bh2u, versiontuple, UserFacingException
from electrum.base_wizard import ScriptTypeNotSupported
from electrum.logging import get_logger
+from electrum.plugin import runs_in_hwd_thread
from ..hw_wallet import HW_PluginBase, HardwareClientBase
from ..hw_wallet.plugin import is_any_tx_output_on_change_branch, validate_op_return_output, LibraryFoundButUnusable
@@ -74,16 +75,14 @@ class Ledger_Client(HardwareClientBase):
def is_pairable(self):
return True
+ @runs_in_hwd_thread
def close(self):
- with self.device_manager().hid_lock:
- self.dongleObject.dongle.close()
-
- def timeout(self, cutoff):
- pass
+ self.dongleObject.dongle.close()
def is_initialized(self):
return True
+ @runs_in_hwd_thread
def get_soft_device_id(self):
if self._soft_device_id is None:
# modern ledger can provide xpub without user interaction
@@ -106,6 +105,7 @@ class Ledger_Client(HardwareClientBase):
return "Ledger Nano X"
return None
+ @runs_in_hwd_thread
def has_usable_connection_with_device(self):
try:
self.dongleObject.getFirmwareVersion()
@@ -113,6 +113,7 @@ class Ledger_Client(HardwareClientBase):
return False
return True
+ @runs_in_hwd_thread
@test_pin_unlocked
def get_xpub(self, bip32_path, xtype):
self.checkDevice()
@@ -180,6 +181,7 @@ class Ledger_Client(HardwareClientBase):
def supports_segwit_trustedInputs(self):
return self.segwitTrustedInputs
+ @runs_in_hwd_thread
def perform_hw1_preflight(self):
try:
firmwareInfo = self.dongleObject.getFirmwareVersion()
@@ -224,6 +226,7 @@ class Ledger_Client(HardwareClientBase):
"Please make sure that 'Browser support' is disabled on your device.")
raise e
+ @runs_in_hwd_thread
def checkDevice(self):
if not self.preflightDone:
try:
@@ -290,6 +293,7 @@ class Ledger_KeyStore(Hardware_KeyStore):
def decrypt_message(self, pubkey, message, password):
raise UserFacingException(_('Encryption and decryption are currently not supported for {}').format(self.device))
+ @runs_in_hwd_thread
@test_pin_unlocked
@set_and_unset_signing
def sign_message(self, sequence, message, password):
@@ -336,6 +340,7 @@ class Ledger_KeyStore(Hardware_KeyStore):
# And convert it
return bytes([27 + 4 + (signature[0] & 0x01)]) + r + s
+ @runs_in_hwd_thread
@test_pin_unlocked
@set_and_unset_signing
def sign_transaction(self, tx, password):
@@ -536,6 +541,7 @@ class Ledger_KeyStore(Hardware_KeyStore):
finally:
self.handler.finished()
+ @runs_in_hwd_thread
@test_pin_unlocked
@set_and_unset_signing
def show_address(self, sequence, txin_type):
@@ -607,6 +613,7 @@ class LedgerPlugin(HW_PluginBase):
else:
raise LibraryFoundButUnusable(library_version=version)
+ @runs_in_hwd_thread
def get_btchip_device(self, device):
ledger = False
if device.product_key[0] == 0x2581 and device.product_key[1] == 0x3b7c:
@@ -618,12 +625,12 @@ class LedgerPlugin(HW_PluginBase):
ledger = True
else:
return None # non-compatible interface of a Nano S or Blue
- with self.device_manager().hid_lock:
- dev = hid.device()
- dev.open_path(device.path)
- dev.set_nonblocking(True)
+ dev = hid.device()
+ dev.open_path(device.path)
+ dev.set_nonblocking(True)
return HIDDongleHIDAPI(dev, ledger, BTCHIP_DEBUG)
+ @runs_in_hwd_thread
def create_client(self, device, handler):
if handler:
self.handler = handler
@@ -648,6 +655,7 @@ class LedgerPlugin(HW_PluginBase):
xpub = client.get_xpub(derivation, xtype)
return xpub
+ @runs_in_hwd_thread
def get_client(self, keystore, force_pair=True, *,
devices=None, allow_user_interaction=True):
# All client interaction should not be in the main GUI thread
@@ -661,6 +669,7 @@ class LedgerPlugin(HW_PluginBase):
client.checkDevice()
return client
+ @runs_in_hwd_thread
def show_address(self, wallet, address, keystore=None):
if keystore is None:
keystore = wallet.get_keystore()
diff --git a/electrum/plugins/safe_t/clientbase.py b/electrum/plugins/safe_t/clientbase.py
@@ -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")
diff --git a/electrum/plugins/safe_t/safe_t.py b/electrum/plugins/safe_t/safe_t.py
@@ -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()
diff --git a/electrum/plugins/trezor/clientbase.py b/electrum/plugins/trezor/clientbase.py
@@ -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)
diff --git a/electrum/plugins/trezor/trezor.py b/electrum/plugins/trezor/trezor.py
@@ -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()