diff --git a/jmclient/jmclient/__init__.py b/jmclient/jmclient/__init__.py index 7dca97f..be7c9fb 100644 --- a/jmclient/jmclient/__init__.py +++ b/jmclient/jmclient/__init__.py @@ -35,7 +35,8 @@ 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, - restart_wait, get_tumble_log) + restart_wait, get_tumble_log, + tumbler_filter_orders_callback) # 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 ec0967e..e25389b 100644 --- a/jmclient/jmclient/tumble_support.py +++ b/jmclient/jmclient/tumble_support.py @@ -168,3 +168,19 @@ def tumbler_taker_finished_update(taker, schedulefile, tumble_log, options, taker.schedule[taker.schedule_index][5] = 1 with open(schedulefile, "wb") as f: f.write(schedule_to_text(taker.schedule)) + +def tumbler_filter_orders_callback(orders_fees, cjamount, taker, options): + """Since the tumbler does not use interactive fee checking, + we use the -x values from the command line instead. + """ + orders, total_cj_fee = orders_fees + abs_cj_fee = 1.0 * total_cj_fee / taker.n_counterparties + rel_cj_fee = abs_cj_fee / cjamount + log.info('rel/abs average fee = ' + str(rel_cj_fee) + ' / ' + str( + abs_cj_fee)) + + if rel_cj_fee > options['maxcjfee'][ + 0] and abs_cj_fee > options['maxcjfee'][1]: + log.info("Rejected fees as too high according to options, will retry.") + return "retry" + return True diff --git a/scripts/joinmarket-qt.py b/scripts/joinmarket-qt.py index 0b2a3f3..d8bf603 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_wait) + get_tumble_log, restart_wait, tumbler_filter_orders_callback) from qtsupport import (ScheduleWizard, warnings, config_tips, config_types, TaskThread, QtHandler, XStream, Buttons, CloseButton, @@ -614,7 +614,9 @@ class SpendTab(QWidget): #sync_wallet(w.wallet, fast=True) #Decide whether to interrupt processing to sanity check the fees - if jm_single().config.get("GUI", "checktx") == "true": + if self.tumbler_options: + check_offers_callback = self.checkOffersTumbler + elif jm_single().config.get("GUI", "checktx") == "true": check_offers_callback = self.callback_checkOffers else: check_offers_callback = None @@ -699,6 +701,10 @@ class SpendTab(QWidget): self.abortTransactions() self.taker_info_response = True + def checkOffersTumbler(self, offers_fees, cjamount): + return tumbler_filter_orders_callback(offers_fees, cjamount, + self.taker, self.tumbler_options) + def checkOffers(self): """Parse offers and total fee from client protocol, allow the user to agree or decide. diff --git a/scripts/qtsupport.py b/scripts/qtsupport.py index 0394eb0..2618281 100644 --- a/scripts/qtsupport.py +++ b/scripts/qtsupport.py @@ -508,24 +508,29 @@ class SchDynamicPage1(QWizardPage): sN = ['Starting mixdepth', 'Average number of counterparties', 'How many mixdepths to tumble through', 'Average wait time between transactions, in minutes', - 'Average number of transactions per mixdepth'] + 'Average number of transactions per mixdepth', + 'Max relative fee per counterparty (e.g. 0.005)', + 'Max fee per counterparty, satoshis (e.g. 10000)'] #Tooltips sH = ["The starting mixdepth can be decided from the Wallet tab; it must " "have coins in it, but it's OK if some coins are in other mixdepths.", "How many other participants are in each coinjoin, on average; but " - "each individual coinjoin will have a number that's slightly varied " - "from this, randomly", + "each individual coinjoin will have a number that's varied according to " + "settings on the next page", "For example, if you start at mixdepth 1 and enter 4 here, the tumble " "will move coins from mixdepth 1 to mixdepth 5", "This is the time waited *after* 1 confirmation has occurred, and is " "varied randomly.", - "Will be varied randomly, with a minimum of 1 per mixdepth"] + "Will be varied randomly, see advanced settings next page", + "A decimal fraction (e.g. 0.001 = 0.1%) (this AND next must be violated to reject", + "Integer number of satoshis (this AND previous must be violated to reject)"] #types - sT = [int, int, int, float, int] + sT = [int, int, int, float, int, float, int] #constraints sMM = [(0, jm_single().config.getint("GUI", "max_mix_depth") - 1), (3, 20), - (1, 5), (0.00000001, 100.0, 8), (2, 10)] - sD = ['', '', '', '', ''] + (1, 5), (0.00000001, 100.0, 8), (2, 10), (0.000001, 0.25, 6), + (0, 10000000)] + sD = ['0', '4', '4', '0.25', '3', '0.005', '10000'] for x in zip(sN, sH, sT, sD, sMM): ql = QLabel(x[0]) ql.setToolTip(x[1]) @@ -541,11 +546,13 @@ class SchDynamicPage1(QWizardPage): layout.addWidget(x[0], i + 1, 0) layout.addWidget(x[1], i + 1, 1, 1, 2) self.setLayout(layout) - self.registerField("mixdepthsrc*", results[0][1]) - self.registerField("makercount*", results[1][1]) - self.registerField("mixdepthcount*", results[2][1]) - self.registerField("timelambda*", results[3][1]) - self.registerField("txcountparams*", results[4][1]) + self.registerField("mixdepthsrc", results[0][1]) + self.registerField("makercount", results[1][1]) + self.registerField("mixdepthcount", results[2][1]) + self.registerField("timelambda", results[3][1]) + self.registerField("txcountparams", results[4][1]) + self.registerField("maxrelfee", results[5][1]) + self.registerField("maxabsfee", results[6][1]) class SchDynamicPage2(QWizardPage): @@ -692,6 +699,9 @@ class ScheduleWizard(QWizard): self.opts['timelambda'] = float(self.field("timelambda").toString()) self.opts['waittime'] = float(self.field("waittime").toString()) self.opts['mincjamount'] = int(self.field("mincjamount").toString()) + relfeeval = float(self.field("maxrelfee").toString()) + absfeeval = int(self.field("maxabsfee").toString()) + self.opts['maxcjfee'] = (relfeeval, absfeeval) #needed for Taker to check: jm_single().mincjamount = self.opts['mincjamount'] return get_tumble_schedule(self.opts, self.destaddrs) diff --git a/scripts/tumbler.py b/scripts/tumbler.py index 26bc13b..074bfa3 100644 --- a/scripts/tumbler.py +++ b/scripts/tumbler.py @@ -19,7 +19,7 @@ from jmclient import (Taker, load_program_config, get_schedule, RegtestBitcoinCoreInterface, estimate_tx_fee, tweak_tumble_schedule, human_readable_schedule_entry, schedule_to_text, restart_waiter, get_tumble_log, - tumbler_taker_finished_update) + tumbler_taker_finished_update, tumbler_filter_orders_callback) from jmbase.support import get_log, debug_dump_object, get_password from cli_options import get_tumbler_parser @@ -101,20 +101,9 @@ def main(): print("Progress logging to logs/TUMBLE.log") def filter_orders_callback(orders_fees, cjamount): - """Since the tumbler does not use interactive fee checking, - we use the -x values from the command line instead. + """Decide whether to accept fees """ - orders, total_cj_fee = orders_fees - abs_cj_fee = 1.0 * total_cj_fee / taker.n_counterparties - rel_cj_fee = abs_cj_fee / cjamount - log.info('rel/abs average fee = ' + str(rel_cj_fee) + ' / ' + str( - abs_cj_fee)) - - if rel_cj_fee > options['maxcjfee'][ - 0] and abs_cj_fee > options['maxcjfee'][1]: - log.info("Rejected fees as too high according to options, will retry.") - return "retry" - return True + return tumbler_filter_orders_callback(orders_fees, cjamount, taker, options) def taker_finished(res, fromtx=False, waittime=0.0, txdetails=None): """on_finished_callback for tumbler; processing is almost entirely