From d0bf888971aa7423e5a2dee3a4c90f035e68b694 Mon Sep 17 00:00:00 2001 From: Adam Gibson Date: Fri, 29 Jul 2022 13:01:59 +0100 Subject: [PATCH] Remove mixdepthsrc from options, more tests --- jmclient/jmclient/cli_options.py | 12 +----------- jmclient/jmclient/schedule.py | 25 ++++++++++++++---------- jmclient/test/test_schedule.py | 9 ++++++++- scripts/tumbler.py | 33 ++++++++++++++++++++++++-------- 4 files changed, 49 insertions(+), 30 deletions(-) diff --git a/jmclient/jmclient/cli_options.py b/jmclient/jmclient/cli_options.py index 13a6cc6..8583d5f 100644 --- a/jmclient/jmclient/cli_options.py +++ b/jmclient/jmclient/cli_options.py @@ -275,14 +275,6 @@ def get_tumbler_parser(): ' be configured to ask for more address mid-run, giving the user' ' a chance to click `Generate New Deposit Address` on whatever service' ' they are using.') - parser.add_option( - '-m', - '--mixdepthsource', - type='int', - dest='mixdepthsrc', - help= - 'Mixing depth to start tumble process from. default=0.', - default=0) parser.add_option('--restart', action='store_true', dest='restart', @@ -398,9 +390,7 @@ def get_tumbler_parser(): type='int', dest='amtmixdepths', help='number of mixdepths ever used in wallet, ' - 'only to be used if mixdepths higher than ' - 'mixdepthsrc + number of mixdepths to tumble ' - 'have been used.', + 'will soon be deprecated.', default=-1) parser.add_option( '--rounding-chance', diff --git a/jmclient/jmclient/schedule.py b/jmclient/jmclient/schedule.py index 5e033d9..0503d50 100644 --- a/jmclient/jmclient/schedule.py +++ b/jmclient/jmclient/schedule.py @@ -17,6 +17,12 @@ from .support import rand_exp_array, rand_norm_array, rand_weighted_choice NO_ROUNDING = 16 #max btc significant figures not including LN +class ScheduleGenerationError(Exception): + pass + +class ScheduleGenerationErrorNoFunds(ScheduleGenerationError): + pass + def get_schedule(filename): with open(filename, "rb") as f: schedule = [] @@ -84,15 +90,11 @@ def get_amount_fractions(count): def get_tumble_schedule(options, destaddrs, mixdepth_balance_dict, max_mixdepth_in_wallet=4): - """for the general intent and design of the tumbler algo, see the docs in - joinmarket-org/joinmarket. - Alterations: - Donation removed for now. + """ Default final setting for "amount_fraction" is zero, for each mixdepth. This is because we now use a general "schedule" syntax for both tumbler and any other taker algo; it interprets floats as fractions and integers as satoshis, and zero as sweep (as before). - This is a modified version of tumbler.py/generate_tumbler_tx() Args: * options - as specified in scripts/tumbler.py and taken from cli_options.py * destaddrs - a list of valid address strings for the destination of funds @@ -136,7 +138,10 @@ def get_tumble_schedule(options, destaddrs, mixdepth_balance_dict, 'rounding': NO_ROUNDING } tx_list.append(tx) - lowest_nonempty_mixdepth = min([x for x, y in nonempty_mixdepths.items() if y == 1]) + try: + lowest_nonempty_mixdepth = min([x for x, y in nonempty_mixdepths.items() if y == 1]) + except ValueError: + raise ScheduleGenerationErrorNoFunds ### stage 2 coinjoins, which create a number of random-amount coinjoins from each mixdepth for m, txcount in enumerate(txcounts): if options['mixdepthcount'] - options['addrcount'] <= m and m < \ @@ -163,8 +168,8 @@ def get_tumble_schedule(options, destaddrs, mixdepth_balance_dict, rounding = rand_weighted_choice(len(weight_prob), weight_prob) + 1 tx = {'amount_fraction': amount_fraction, 'wait': round(wait, 2), - 'srcmixdepth': (lowest_nonempty_mixdepth + m + options[ - 'mixdepthsrc']) % (max_mixdepth_in_wallet + 1), + 'srcmixdepth': (lowest_nonempty_mixdepth + m) % ( + max_mixdepth_in_wallet + 1), 'makercount': makercount, 'destination': 'INTERNAL', 'rounding': rounding @@ -176,8 +181,8 @@ def get_tumble_schedule(options, destaddrs, mixdepth_balance_dict, addrask = options['addrcount'] - len(destaddrs) external_dest_addrs = ['addrask'] * addrask + destaddrs[::-1] for mix_offset in range(options['addrcount']): - srcmix = (lowest_nonempty_mixdepth + options['mixdepthsrc'] - + options['mixdepthcount'] - mix_offset - 1) % (max_mixdepth_in_wallet + 1) + srcmix = (lowest_nonempty_mixdepth + options['mixdepthcount'] + - mix_offset - 1) % (max_mixdepth_in_wallet + 1) for tx in reversed(tx_list): if tx['srcmixdepth'] == srcmix: tx['destination'] = external_dest_addrs[mix_offset] diff --git a/jmclient/test/test_schedule.py b/jmclient/test/test_schedule.py index 7cb7c2f..8b873eb 100644 --- a/jmclient/test/test_schedule.py +++ b/jmclient/test/test_schedule.py @@ -54,7 +54,6 @@ class Options(object): def get_options(): options = Options() - options.mixdepthsrc = 0 options.mixdepthcount = 4 options.txcountparams = (18, 3) options.minmakercount = 2 @@ -90,6 +89,13 @@ def get_options(): "mifCWfmygxKhsP3qM3HZi3ZjBEJu7m39h8", "mnTn9KVQQT9zy9R4E2ZGzWPK4EfcEcV9Y5"], (3,2), 8, {2:1, 3: 1}), + #slightly larger version + (["mzzAYbtPpANxpNVGCVBAhZYzrxyZtoix7i", + "mifCWfmygxKhsP3qM3HZi3ZjBEJu7m39h8", + "mnTn9KVQQT9zy9R4E2ZGzWPK4EfcEcV9Y5", + "bcrt1qcnv26w889eum5sekz5h8we45rxnr4sj5k08phv", + "bcrt1qgs0t239gj2kqgnsrvetvsv2qdva8y3j74cta4d"], (4,3), 8, + {0:2, 1: 1, 3: 1, 4: 1}), ]) def test_tumble_schedule(destaddrs, txcparams, mixdepthcount, mixdepthbal): # note that these tests are currently only leaving the default @@ -97,6 +103,7 @@ def test_tumble_schedule(destaddrs, txcparams, mixdepthcount, mixdepthbal): # and will fail if this is changed: wallet_total_mixdepths = 5 options = get_options() + options['addrcount'] = len(destaddrs) options['mixdepthcount'] = mixdepthcount options['txcountparams'] = txcparams schedule = get_tumble_schedule(options, destaddrs, mixdepthbal) diff --git a/scripts/tumbler.py b/scripts/tumbler.py index 825c44d..ef79a05 100755 --- a/scripts/tumbler.py +++ b/scripts/tumbler.py @@ -12,6 +12,8 @@ from jmclient import Taker, load_program_config, get_schedule,\ get_tumble_log, tumbler_taker_finished_update, check_regtest, \ tumbler_filter_orders_callback, validate_address, get_tumbler_parser, \ get_max_cj_fee_values +from jmclient.wallet_utils import DEFAULT_MIXDEPTH +from jmclient.schedule import ScheduleGenerationErrorNoFunds from jmbase.support import get_log, jmprint, EXIT_SUCCESS, \ EXIT_FAILURE, EXIT_ARGERROR @@ -38,11 +40,17 @@ def main(): #Load the wallet wallet_name = args[0] - max_mix_depth = options['mixdepthsrc'] + options['mixdepthcount'] - if options['amtmixdepths'] > max_mix_depth: + # as of #1324 the concept of a max_mix_depth distinct from + # the normal wallet value (4) no longer applies, since the + # tumbler cycles; but we keep the `amtmixdepths` option for now, + # deprecating it later. + if options['amtmixdepths'] > DEFAULT_MIXDEPTH: max_mix_depth = options['amtmixdepths'] + else: + max_mix_depth = DEFAULT_MIXDEPTH wallet_path = get_wallet_path(wallet_name, None) - wallet = open_test_wallet_maybe(wallet_path, wallet_name, max_mix_depth, wallet_password_stdin=options_org.wallet_password_stdin) + wallet = open_test_wallet_maybe(wallet_path, wallet_name, max_mix_depth, + wallet_password_stdin=options_org.wallet_password_stdin) wallet_service = WalletService(wallet) if wallet_service.rpc_error: sys.exit(EXIT_FAILURE) @@ -109,8 +117,12 @@ def main(): tumble_log.info("TUMBLE RESTARTING") else: #Create a new schedule from scratch - schedule = get_tumble_schedule(options, destaddrs, - wallet.get_balance_by_mixdepth(), wallet_service.mixdepth) + try: + schedule = get_tumble_schedule(options, destaddrs, + wallet.get_balance_by_mixdepth(), wallet_service.mixdepth) + except ScheduleGenerationErrorNoFunds: + jmprint("No funds in wallet to tumble.", "error") + sys.exit(EXIT_FAILURE) tumble_log.info("TUMBLE STARTING") with open(os.path.join(logsdir, options['schedulefile']), "wb") as f: f.write(schedule_to_text(schedule)) @@ -133,10 +145,15 @@ def main(): involved_parties = len(schedule) # own participation in each CJ for item in schedule: involved_parties += item[2] # number of total tumble counterparties + # calculating total coins that will be included in the tumble; + # in almost all cases all coins (unfrozen) in wallet will be tumbled, + # though it's technically possible with a very small mixdepthcount, to start + # at say m0, and only go through to 2 or 3, such that coins in 4 are untouched + # in phase 2 (after having been swept in phase 1). + used_mixdepths = set() + [used_mixdepths.add(x[0]) for x in schedule] total_tumble_amount = int(0) - max_mix_to_tumble = min(options['mixdepthsrc']+options['mixdepthcount'], \ - max_mix_depth) - for i in range(options['mixdepthsrc'], max_mix_to_tumble): + for i in used_mixdepths: total_tumble_amount += wallet_service.get_balance_by_mixdepth()[i] if total_tumble_amount == 0: raise ValueError("No confirmed coins in the selected mixdepth(s). Quitting")