tcosigner pool: use single thread to send messages - electrum - Electrum Bitcoin wallet HTML git clone https://git.parazyd.org/electrum DIR Log DIR Files DIR Refs DIR Submodules --- DIR commit 7a6ec23b6ecfe48e28e16d2a9cd1bb255ead75c8 DIR parent 1a9e6a434f9fb5277c0813e2438805e1f23397be HTML Author: SomberNight <somber.night@protonmail.com> Date: Mon, 29 Jun 2020 02:19:03 +0200 cosigner pool: use single thread to send messages ServerProxy does not seem to be thread-safe. For e.g. a 2of3 multisig wallet, which would send two messages, one msg would get sent but the other might error out. See trace: E | plugins.cosigner_pool.qt.Plugin | on_failure Traceback (most recent call last): File "...\electrum\electrum\gui\qt\util.py", line 832, in run result = task.task() File "...\electrum\electrum\plugins\cosigner_pool\qt.py", line 199, in <lambda> task = lambda: server.put(_hash, message) File "...\Python38\lib\xmlrpc\client.py", line 1109, in __call__ return self.__send(self.__name, args) File "...\Python38\lib\xmlrpc\client.py", line 1450, in __request response = self.__transport.request( File "...\Python38\lib\xmlrpc\client.py", line 1153, in request return self.single_request(host, handler, request_body, verbose) File "...\Python38\lib\xmlrpc\client.py", line 1165, in single_request http_conn = self.send_request(host, handler, request_body, verbose) File "...\Python38\lib\xmlrpc\client.py", line 1271, in send_request connection.putrequest("POST", handler, skip_accept_encoding=True) File "...\Python38\lib\http\client.py", line 1088, in putrequest raise CannotSendRequest(self.__state) http.client.CannotSendRequest: Request-sent Diffstat: M electrum/plugins/cosigner_pool/qt.… | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) --- DIR diff --git a/electrum/plugins/cosigner_pool/qt.py b/electrum/plugins/cosigner_pool/qt.py t@@ -188,17 +188,27 @@ class Plugin(BasePlugin): except OSError: pass window.show_error(_("Failed to send transaction to cosigning pool") + ':\n' + repr(e)) + buffer = [] + some_window = None + # construct messages for window, xpub, K, _hash in self.cosigner_list: if not self.cosigner_can_sign(tx, xpub): continue - # construct message + some_window = window raw_tx_bytes = tx.serialize_as_bytes() public_key = ecc.ECPubkey(K) message = public_key.encrypt_message(raw_tx_bytes).decode('ascii') - # send message - task = lambda: server.put(_hash, message) - msg = _('Sending transaction to cosigning pool...') - WaitingDialog(window, msg, task, on_success, on_failure) + buffer.append((_hash, message)) + if not buffer: + return + + # send messages + # note: we send all messages sequentially on the same thread + def send_messages_task(): + for _hash, message in buffer: + server.put(_hash, message) + msg = _('Sending transaction to cosigning pool...') + WaitingDialog(some_window, msg, send_messages_task, on_success, on_failure) def on_receive(self, keyhash, message): self.logger.info(f"signal arrived for {keyhash}")