From 04c8b6655fcca9493189dccd6cead921a0744450 Mon Sep 17 00:00:00 2001 From: Adam Gibson Date: Sat, 25 Feb 2017 17:06:15 +0200 Subject: [PATCH] support restart in Qt --- jmclient/jmclient/__init__.py | 2 +- jmclient/jmclient/tumble_support.py | 21 +++++++--- scripts/joinmarket-qt.py | 61 ++++++++++++++++++++++++++++- 3 files changed, 75 insertions(+), 9 deletions(-) diff --git a/jmclient/jmclient/__init__.py b/jmclient/jmclient/__init__.py index 1d60918..7dca97f 100644 --- a/jmclient/jmclient/__init__.py +++ b/jmclient/jmclient/__init__.py @@ -35,7 +35,7 @@ from .schedule import (get_schedule, get_tumble_schedule, schedule_to_text, schedule_to_text) from .commitment_utils import get_utxo_info, validate_utxo_data, quit from .tumble_support import (tumbler_taker_finished_update, restart_waiter, - get_tumble_log) + restart_wait, get_tumble_log) # Set default logging handler to avoid "No handler found" warnings. try: diff --git a/jmclient/jmclient/tumble_support.py b/jmclient/jmclient/tumble_support.py index 4c52d1b..ec0967e 100644 --- a/jmclient/jmclient/tumble_support.py +++ b/jmclient/jmclient/tumble_support.py @@ -24,10 +24,22 @@ def get_tumble_log(logsdir): tumble_log.addHandler(fileHandler) return tumble_log +def restart_wait(txid): + """Here txid is of form txid:N for direct utxo query. + Returns true only if the utxo is reported to have at least 1 + confirm by the blockchain interface. + """ + res = jm_single().bc_interface.query_utxo_set(txid, includeconf=True) + if not res[0]: + return False + if res[0]['confirms'] > 0: + return True + return False + def restart_waiter(txid): """Given a txid, wait for confirmation by polling the blockchain - interface instance. Note that this is currently blocking, which is - fine for the CLI for now, but should be re-done using twisted/thread TODO. + interface instance. Note that this is currently blocking, so only used + by the CLI version; the Qt/GUI uses the underlying restart_wait() fn. """ ctr = 0 log.info("Waiting for confirmation of last transaction: " + str(txid)) @@ -36,10 +48,7 @@ def restart_waiter(txid): ctr += 1 if not (ctr % 12): log.debug("Still waiting for confirmation of last transaction ...") - res = jm_single().bc_interface.query_utxo_set(txid, includeconf=True) - if not res[0]: - continue - if res[0]['confirms'] > 0: + if restart_wait(txid): break log.info("The previous transaction is now in a block; continuing.") diff --git a/scripts/joinmarket-qt.py b/scripts/joinmarket-qt.py index dba8e05..ae8dd53 100644 --- a/scripts/joinmarket-qt.py +++ b/scripts/joinmarket-qt.py @@ -53,7 +53,7 @@ from jmclient import (load_program_config, get_network, Wallet, get_blockchain_interface_instance, sync_wallet, RegtestBitcoinCoreInterface, tweak_tumble_schedule, human_readable_schedule_entry, tumbler_taker_finished_update, - get_tumble_log, restart_waiter) + get_tumble_log, restart_wait) from qtsupport import (ScheduleWizard, warnings, config_tips, config_types, TaskThread, QtHandler, XStream, Buttons, CloseButton, @@ -303,6 +303,7 @@ class SpendTab(QWidget): self.tumbler_options = None #signals from client backend to GUI self.jmclient_obj = QtCore.QObject() + self.restartTimer = QtCore.QTimer() #This signal/callback requires user acceptance decision. self.jmclient_obj.connect(self.jmclient_obj, QtCore.SIGNAL('JMCLIENT:offers'), self.checkOffers) @@ -353,6 +354,14 @@ class SpendTab(QWidget): self.spendstate.loaded_schedule = schedule self.spendstate.schedule_name = os.path.basename(str(firstarg)) self.updateSchedView() + if self.spendstate.schedule_name == "TUMBLE.schedule": + reply = JMQtMessageBox(self, "An incomplete tumble run detected. " + "\nDo you want to restart?", + title="Restart detected", mbtype='question') + if reply != QMessageBox.Yes: + self.giveUp() + return + self.tumbler_options = True def updateSchedView(self): self.sch_label2.setText(self.spendstate.schedule_name) @@ -501,6 +510,13 @@ class SpendTab(QWidget): def resizeScroll(self, mini, maxi): self.textedit.verticalScrollBar().setValue(maxi) + def restartWaitWrap(self): + if restart_wait(self.waitingtxid): + self.restartTimer.stop() + self.waitingtxid = None + w.statusBar().showMessage("Transaction in a block, now continuing.") + self.startJoin() + def startMultiple(self): if not self.spendstate.runstate == 'ready': log.info("Cannot start join, already running.") @@ -508,9 +524,48 @@ class SpendTab(QWidget): if not self.spendstate.loaded_schedule: log.info("Cannot start, no schedule loaded.") return - #self.qtw.setTabEnabled(0, False) self.spendstate.updateType('multiple') self.spendstate.updateRun('running') + + if self.tumbler_options: + #TODO: mincjamount is the only tumbler setting, except maxcjfee, + #that is not part of sched-generation, so request from user + if not hasattr(jm_single(), 'mincjamount'): + mincjamount, ok = QInputDialog.getInt(self, + "Set min coinjoin amount", + "Enter minimum allowable coinjoin amount in satoshis") + if not ok: + self.giveUp() + return + jm_single().mincjamount = mincjamount + #check for a partially-complete schedule; if so, + #follow restart logic + #1. filter out complete: + self.spendstate.loaded_schedule = [ + s for s in self.spendstate.loaded_schedule if s[5] != 1] + #reload destination addresses + self.tumbler_destaddrs = [x[3] for x in self.spendstate.loaded_schedule + if x not in ["INTERNAL", "addrask"]] + #2 Check for unconfirmed + if isinstance(self.spendstate.loaded_schedule[0][5], str) and len( + self.spendstate.loaded_schedule[0][5]) == 64: + #ensure last transaction is confirmed before restart + tumble_log.info("WAITING TO RESTART...") + w.statusBar().showMessage("Waiting for confirmation to restart..") + txid = self.spendstate.loaded_schedule[0][5] + #remove the already-done entry (this connects to the other TODO, + #probably better *not* to truncate the done-already txs from file, + #but simplest for now. + self.spendstate.loaded_schedule = self.spendstate.loaded_schedule[1:] + #defers startJoin() call until tx seen on network. Note that + #since we already updated state to running, user cannot + #start another transactions while waiting. Also, use :0 because + #it always exists + self.waitingtxid=txid+":0" + self.restartTimer.timeout.connect(self.restartWaitWrap) + self.restartTimer.start(5000) + return + self.updateSchedView() self.startJoin() def startSingle(self): @@ -1525,6 +1580,8 @@ update_config_for_gui() if isinstance(jm_single().bc_interface, RegtestBitcoinCoreInterface): jm_single().bc_interface.tick_forward_chain_interval = 10 jm_single().maker_timeout_sec = 5 + #trigger start with a fake tx + jm_single().bc_interface.pushtx("00"*20) #prepare for logging for dname in ['logs', 'wallets', 'cmtdata']: