def main(): load_program_config() parser = OptionParser( usage='usage: %prog [options]', description='Runs a webservice which shows the orderbook.') parser.add_option('-H', '--host', action='store', type='string', dest='host', default='localhost', help='hostname or IP to bind to, default=localhost') parser.add_option('-p', '--port', action='store', type='int', dest='port', help='port to listen on, default=62601', default=62601) (options, args) = parser.parse_args() hostport = (options.host, options.port) mcs = [IRCMessageChannel(c) for c in get_irc_mchannels()] mcc = MessageChannelCollection(mcs) log.info("Starting ob-watcher") # todo: is the call to GUITaker needed, or the return. taker unused taker = GUITaker(mcc, hostport) print('starting irc') mcc.run()
def main(): parser = OptionParser( usage='usage: %prog [options] [tx hex]', description= 'Sends a transaction to a random market maker requesting that they broadcast it ' + 'to the wider bitcoin network. Used to add a layer between your own IP address and the network ' + 'where other methods are not possible.') parser.add_option( '-w', '--wait-time', action='store', type='float', dest='waittime', help='wait time in seconds to allow orders to arrive, default=5', default=10) (options, args) = parser.parse_args() if len(args) < 1: parser.error('Needs a transaction hex string') sys.exit(0) txhex = args[0] load_program_config() jm_single().nickname = random_nick() log.debug('starting broadcast-tx') mcs = [ IRCMessageChannel(c, jm_single().nickname) for c in get_irc_mchannels() ] mcc = MessageChannelCollection(mcs) taker = Broadcaster(mcc, options.waittime, txhex) try: log.debug('starting message channels') mcc.run() except: log.debug('CRASHING, DUMPING EVERYTHING') debug_dump_object(taker) import traceback log.debug(traceback.format_exc())
def main(): parser = OptionParser( usage= 'usage: %prog [options] [tx hex]', description='Sends a transaction to a random market maker requesting that they broadcast it ' + 'to the wider bitcoin network. Used to add a layer between your own IP address and the network ' + 'where other methods are not possible.') parser.add_option( '-w', '--wait-time', action='store', type='float', dest='waittime', help='wait time in seconds to allow orders to arrive, default=5', default=10) (options, args) = parser.parse_args() if len(args) < 1: parser.error('Needs a transaction hex string') sys.exit(0) txhex = args[0] load_program_config() jm_single().nickname = random_nick() log.debug('starting broadcast-tx') mcs = [IRCMessageChannel(c, jm_single().nickname) for c in get_irc_mchannels()] mcc = MessageChannelCollection(mcs) taker = Broadcaster(mcc, options.waittime, txhex) try: log.debug('starting message channels') mcc.run() except: log.debug('CRASHING, DUMPING EVERYTHING') debug_dump_object(taker) import traceback log.debug(traceback.format_exc())
def test_tx_commitments_used(setup_podle, consume_tx, age_required, cmt_age): tries = jm_single().config.getint("POLICY","taker_utxo_retries") #remember and reset at the end taker_utxo_age = jm_single().config.getint("POLICY", "taker_utxo_age") jm_single().config.set("POLICY", "taker_utxo_age", str(age_required)) #Don't want to wait too long, but must account for possible #throttling with !auth jm_single().maker_timeout_sec = 12 amount = 0 wallets = make_wallets(3, wallet_structures=[[1,2,1,0,0],[1,2,0,0,0],[2,2,1,0,0]], mean_amt=1) #the sendpayment bot uses the last wallet in the list wallet = wallets[2]['wallet'] #make_wallets calls grab_coins which mines 1 block per individual payout, #so the age of the coins depends on where they are in that list. The sendpayment #is the last wallet in the list, and we choose the non-tx utxos which are in #mixdepth 1 and 2 (2 and 1 utxos in each respectively). We filter for those #that have sufficient age, so to get 1 which is old enough, it will be the oldest, #which will have an age of 2 + 1 (the first utxo spent to that wallet). #So if we need an age of 6, we need to mine 3 more blocks. blocks_reqd = cmt_age - 3 jm_single().bc_interface.tick_forward_chain(blocks_reqd) yigen_procs = [] for i in range(2): ygp = local_command([python_cmd, yg_cmd,\ str(wallets[i]['seed'])], bg=True) time.sleep(2) #give it a chance yigen_procs.append(ygp) time.sleep(5) destaddr = btc.privkey_to_address( binascii.hexlify(os.urandom(32)), magicbyte=get_p2pk_vbyte()) addr_valid, errormsg = validate_address(destaddr) assert addr_valid, "Invalid destination address: " + destaddr + \ ", error message: " + errormsg log.debug('starting sendpayment') jm_single().bc_interface.sync_wallet(wallet) log.debug("Here is the whole wallet: \n" + str(wallet.unspent)) #Trigger PING LAG sending artificially joinmarket.irc.PING_INTERVAL = 3 mcs = [IRCMessageChannel(c) for c in get_irc_mchannels()] mcc = MessageChannelCollection(mcs) if consume_tx: #add all utxo in mixdepth 0 to 'used' list of commitments, utxos = wallet.get_utxos_by_mixdepth()[0] for u, addrval in utxos.iteritems(): priv = wallet.get_key_from_addr(addrval['address']) podle = btc.PoDLE(u, priv) for i in range(tries): #loop because we want to use up all retries of this utxo commitment = podle.generate_podle(i)['commit'] btc.update_commitments(commitment=commitment) #Now test a sendpayment from mixdepth 0 with all the depth 0 utxos #used up, so that the other utxos in the wallet get used. taker = sendpayment.SendPayment(mcc, wallet, destaddr, amount, 2, 5000, 3, 0, True, weighted_order_choose) try: log.debug('starting message channels') mcc.run() finally: if any(yigen_procs): for ygp in yigen_procs: #NB *GENTLE* shutdown is essential for #test coverage reporting! ygp.send_signal(signal.SIGINT) ygp.wait() #wait for block generation time.sleep(5) received = jm_single().bc_interface.get_received_by_addr( [destaddr], None)['data'][0]['balance'] jm_single().config.set("POLICY", "taker_utxo_age", str(taker_utxo_age)) if cmt_age < age_required: assert received == 0, "Coins arrived but shouldn't" else: assert received != 0, "sendpayment failed - coins not arrived, " +\ "received: " + str(received)
def test_external_commitment_used(setup_podle): tries = jm_single().config.getint("POLICY","taker_utxo_retries") #Don't want to wait too long, but must account for possible #throttling with !auth jm_single().maker_timeout_sec = 12 amount = 50000000 wallets = make_wallets(3, wallet_structures=[[1,0,0,0,0],[1,0,0,0,0],[1,1,0,0,0]], mean_amt=1) #the sendpayment bot uses the last wallet in the list wallet = wallets[2]['wallet'] yigen_procs = [] for i in range(2): ygp = local_command([python_cmd, yg_cmd,\ str(wallets[i]['seed'])], bg=True) time.sleep(2) #give it a chance yigen_procs.append(ygp) #A significant delay is needed to wait for the yield generators to sync time.sleep(10) destaddr = btc.privkey_to_address( binascii.hexlify(os.urandom(32)), magicbyte=get_p2pk_vbyte()) addr_valid, errormsg = validate_address(destaddr) assert addr_valid, "Invalid destination address: " + destaddr + \ ", error message: " + errormsg log.debug('starting sendpayment') jm_single().bc_interface.sync_wallet(wallet) #Trigger PING LAG sending artificially joinmarket.irc.PING_INTERVAL = 3 mcs = [IRCMessageChannel(c) for c in get_irc_mchannels()] mcc = MessageChannelCollection(mcs) #add all utxo in mixdepth 0 to 'used' list of commitments, utxos = wallet.get_utxos_by_mixdepth()[0] for u, addrval in utxos.iteritems(): priv = wallet.get_key_from_addr(addrval['address']) podle = btc.PoDLE(u, priv) for i in range(tries): #loop because we want to use up all retries of this utxo commitment = podle.generate_podle(i)['commit'] btc.update_commitments(commitment=commitment) #create a new utxo, notionally from an external source; to make life a little #easier we'll pay to another mixdepth, but this is OK because #taker does not source from here currently, only from the utxos chosen #for the transaction, not the whole wallet. So we can treat it as if #external (don't access its privkey). utxos = wallet.get_utxos_by_mixdepth()[1] ecs = {} for u, addrval in utxos.iteritems(): priv = wallet.get_key_from_addr(addrval['address']) ecs[u] = {} ecs[u]['reveal']={} for j in range(tries): P, P2, s, e, commit = generate_single_podle_sig( binascii.unhexlify(priv), j) if 'P' not in ecs[u]: ecs[u]['P'] = P ecs[u]['reveal'][j] = {'P2':P2, 's':s, 'e':e} btc.update_commitments(external_to_add=ecs) #Now the conditions described above hold. We do a normal single #sendpayment. taker = sendpayment.SendPayment(mcc, wallet, destaddr, amount, 2, 5000, 3, 0, True, weighted_order_choose) try: log.debug('starting message channels') mcc.run() finally: if any(yigen_procs): for ygp in yigen_procs: #NB *GENTLE* shutdown is essential for #test coverage reporting! ygp.send_signal(signal.SIGINT) ygp.wait() #wait for block generation time.sleep(5) received = jm_single().bc_interface.get_received_by_addr( [destaddr], None)['data'][0]['balance'] assert received == amount, "sendpayment failed - coins not arrived, " +\ "received: " + str(received) #Cleanup - remove the external commitments added btc.update_commitments(external_to_remove=ecs)
def test_failed_sendpayment(setup_podle, num_ygs, wallet_structures, mean_amt, mixdepth, sending_amt): """Test of initiating joins, but failing to complete, to see commitment usage. YGs in background as per test_regtest. Use sweeps to avoid recover_from_nonrespondants without intruding into sendpayment code. """ makercount = num_ygs answeryes = True txfee = 5000 waittime = 3 #Don't want to wait too long, but must account for possible #throttling with !auth jm_single().maker_timeout_sec = 12 amount = 0 wallets = make_wallets(makercount + 1, wallet_structures=wallet_structures, mean_amt=mean_amt) #the sendpayment bot uses the last wallet in the list wallet = wallets[makercount]['wallet'] yigen_procs = [] for i in range(makercount): ygp = local_command([python_cmd, yg_cmd,\ str(wallets[i]['seed'])], bg=True) time.sleep(2) #give it a chance yigen_procs.append(ygp) #A significant delay is needed to wait for the yield generators to sync time.sleep(20) destaddr = btc.privkey_to_address( os.urandom(32), from_hex=False, magicbyte=get_p2pk_vbyte()) addr_valid, errormsg = validate_address(destaddr) assert addr_valid, "Invalid destination address: " + destaddr + \ ", error message: " + errormsg #TODO paramatetrize this as a test variable chooseOrdersFunc = weighted_order_choose log.debug('starting sendpayment') jm_single().bc_interface.sync_wallet(wallet) #Trigger PING LAG sending artificially joinmarket.irc.PING_INTERVAL = 3 mcs = [IRCMessageChannel(c) for c in get_irc_mchannels()] mcc = MessageChannelCollection(mcs) #Allow taker more retries than makers allow, so as to trigger #blacklist failure case jm_single().config.set("POLICY", "taker_utxo_retries", "4") #override ioauth receipt with a dummy do-nothing callback: def on_ioauth(*args): log.debug("Taker received: " + ','.join([str(x) for x in args])) class DummySendPayment(sendpayment.SendPayment): def __init__(self, msgchan, wallet, destaddr, amount, makercount, txfee, waittime, mixdepth, answeryes, chooseOrdersFunc, on_ioauth): self.on_ioauth = on_ioauth self.podle_fails = 0 self.podle_allowed_fails = 3 #arbitrary; but do it more than once self.retries = 0 super(DummySendPayment, self).__init__(msgchan, wallet, destaddr, amount, makercount, txfee, waittime, mixdepth, answeryes, chooseOrdersFunc) def on_welcome(self): Taker.on_welcome(self) DummyPaymentThread(self).start() class DummyPaymentThread(sendpayment.PaymentThread): def finishcallback(self, coinjointx): #Don't ignore makers and just re-start self.taker.retries += 1 if self.taker.podle_fails == self.taker.podle_allowed_fails: self.taker.msgchan.shutdown() return self.create_tx() def create_tx(self): try: super(DummyPaymentThread, self).create_tx() except btc.PoDLEError: log.debug("Got one commit failure, continuing") self.taker.podle_fails += 1 taker = DummySendPayment(mcc, wallet, destaddr, amount, makercount, txfee, waittime, mixdepth, answeryes, chooseOrdersFunc, on_ioauth) try: log.debug('starting message channels') mcc.run() finally: if any(yigen_procs): for ygp in yigen_procs: #NB *GENTLE* shutdown is essential for #test coverage reporting! ygp.send_signal(signal.SIGINT) ygp.wait() #We should have been able to try (tur -1) + podle_allowed_fails times assert taker.retries == jm_single().config.getint( "POLICY", "taker_utxo_retries") + taker.podle_allowed_fails #wait for block generation time.sleep(2) received = jm_single().bc_interface.get_received_by_addr( [destaddr], None)['data'][0]['balance'] #Sanity check no transaction succeeded assert received == 0
def main(): parser = OptionParser( usage='usage: %prog [options] [wallet file] [destaddr(s)...]', description= 'Sends bitcoins to many different addresses using coinjoin in' ' an attempt to break the link between them. Sending to multiple ' ' addresses is highly recommended for privacy. This tumbler can' ' 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 spend from. Useful if a previous tumbler run prematurely ended with ' + 'coins being left in higher mixing levels, this option can be used to resume without needing' + ' to send to another address. default=0', default=0) parser.add_option( '-f', '--txfee', action='store', type='int', dest='txfee', default=-1, help= 'number of satoshis per participant to use as the initial estimate ' + 'for the total transaction fee, default=dynamically estimated, note that this is adjusted ' + 'based on the estimated fee calculated after tx construction, based on ' + 'policy set in joinmarket.cfg.') parser.add_option( '-a', '--addrcount', type='int', dest='addrcount', default=3, help= 'How many destination addresses in total should be used. If not enough are given' ' as command line arguments, the script will ask for more. This parameter is required' ' to stop amount correlation. default=3') parser.add_option( '-x', '--maxcjfee', type='float', dest='maxcjfee', nargs=2, default=(0.01, 10000), help='maximum coinjoin fee and bitcoin value the tumbler is ' 'willing to pay to a single market maker. Both values need to be exceeded, so if ' 'the fee is 30% but only 500satoshi is paid the tx will go ahead. default=0.01, 10000 (1%, 10000satoshi)' ) parser.add_option( '-N', '--makercountrange', type='float', nargs=2, action='store', dest='makercountrange', help= 'Input the mean and spread of number of makers to use. e.g. 6 1 will be a normal distribution ' 'with mean 6 and standard deviation 1 inclusive, default=6 1 (floats are also OK)', default=(6, 1)) parser.add_option( '--minmakercount', type='int', dest='minmakercount', default=4, help= 'The minimum maker count in a transaction, random values below this are clamped at this number. default=4' ) parser.add_option('-M', '--mixdepthcount', type='int', dest='mixdepthcount', help='How many mixing depths to mix through', default=4) parser.add_option( '-c', '--txcountparams', type='float', nargs=2, dest='txcountparams', default=(4, 1), help= 'The number of transactions to take coins from one mixing depth to the next, it is' ' randomly chosen following a normal distribution. Should be similar to --addrask. ' 'This option controls the parameters of the normal distribution curve. (mean, standard deviation). default=4 1' ) parser.add_option( '--mintxcount', type='int', dest='mintxcount', default=1, help='The minimum transaction count per mixing level, default=1') parser.add_option( '--donateamount', type='float', dest='donateamount', default=0, help= 'percent of funds to donate to joinmarket development, or zero to opt out (default=0%)' ) parser.add_option( '--amountpower', type='float', dest='amountpower', default=100.0, help= 'The output amounts follow a power law distribution, this is the power, default=100.0' ) parser.add_option( '-l', '--timelambda', type='float', dest='timelambda', default=30, help= 'Average the number of minutes to wait between transactions. Randomly chosen ' ' following an exponential distribution, which describes the time between uncorrelated' ' events. default=30') parser.add_option( '-w', '--wait-time', action='store', type='float', dest='waittime', help='wait time in seconds to allow orders to arrive, default=20', default=20) parser.add_option( '-s', '--mincjamount', type='int', dest='mincjamount', default=100000, help='minimum coinjoin amount in transaction in satoshi, default 100k') parser.add_option( '-q', '--liquiditywait', type='int', dest='liquiditywait', default=60, help= 'amount of seconds to wait after failing to choose suitable orders before trying again, default 60' ) parser.add_option( '--maxbroadcasts', type='int', dest='maxbroadcasts', default=4, help= 'maximum amount of times to broadcast a transaction before giving up and re-creating it, default 4' ) parser.add_option( '--maxcreatetx', type='int', dest='maxcreatetx', default=9, help= 'maximum amount of times to re-create a transaction before giving up, default 9' ) parser.add_option('--fast', action='store_true', dest='fastsync', default=False, help=('choose to do fast wallet sync, only for Core and ' 'only for previously synced wallet')) (options, args) = parser.parse_args() options = vars(options) if len(args) < 1: parser.error('Needs a wallet file') sys.exit(0) wallet_file = args[0] destaddrs = args[1:] print(destaddrs) load_program_config() #The minmakercount setting should not be lower than the #minimum allowed makers according to the config if options['minmakercount'] < jm_single().config.getint( "POLICY", "minimum_makers"): log.error("You selected a minimum number of counterparties (" + \ str(options['minmakercount']) + \ ") less than the " "minimum requirement (" + \ str(jm_single().config.getint("POLICY","minimum_makers")) + \ "); you can edit the value 'minimum_makers'" " in the POLICY section in joinmarket.cfg to correct this. " "Quitting.") exit(0) for addr in destaddrs: addr_valid, errormsg = validate_address(addr) if not addr_valid: print('ERROR: Address ' + addr + ' invalid. ' + errormsg) return # Dynamically estimate a realistic fee if it currently is the default value. # At this point we do not know even the number of our own inputs, so # we guess conservatively with 2 inputs and 2 outputs each if options['txfee'] == -1: options['txfee'] = max(options['txfee'], estimate_tx_fee(2, 2)) log.info("Estimated miner/tx fee for each cj participant: " + str(options['txfee'])) assert (options['txfee'] >= 0) if len(destaddrs) > options['addrcount']: options['addrcount'] = len(destaddrs) if options['addrcount'] + 1 > options['mixdepthcount']: print('not enough mixing depths to pay to all destination addresses, ' 'increasing mixdepthcount') options['mixdepthcount'] = options['addrcount'] + 1 if options['donateamount'] > 10.0: # fat finger probably, or misunderstanding options['donateamount'] = 0.9 print(str(options)) tx_list = generate_tumbler_tx(destaddrs, options) if not tx_list: return tx_list2 = copy.deepcopy(tx_list) tx_dict = {} for tx in tx_list2: srcmixdepth = tx['srcmixdepth'] tx.pop('srcmixdepth') if srcmixdepth not in tx_dict: tx_dict[srcmixdepth] = [] tx_dict[srcmixdepth].append(tx) dbg_tx_list = [] for srcmixdepth, txlist in tx_dict.iteritems(): dbg_tx_list.append({'srcmixdepth': srcmixdepth, 'tx': txlist}) log.info('tumbler transaction list') pprint(dbg_tx_list) total_wait = sum([tx['wait'] for tx in tx_list]) print('creates ' + str(len(tx_list)) + ' transactions in total') print('waits in total for ' + str(len(tx_list)) + ' blocks and ' + str(total_wait) + ' minutes') total_block_and_wait = len(tx_list) * 10 + total_wait print('estimated time taken ' + str(total_block_and_wait) + ' minutes or ' + str(round(total_block_and_wait / 60.0, 2)) + ' hours') if options['addrcount'] <= 1: print('=' * 50) print('WARNING: You are only using one destination address') print('this is very bad for privacy') print('=' * 50) ret = raw_input('tumble with these tx? (y/n):') if ret[0] != 'y': return # NOTE: possibly out of date documentation # a couple of modes # im-running-from-the-nsa, takes about 80 hours, costs a lot # python tumbler.py -a 10 -N 10 5 -c 10 5 -l 50 -M 10 wallet_file 1xxx # # quick and cheap, takes about 90 minutes # python tumbler.py -N 2 1 -c 3 0.001 -l 10 -M 3 -a 1 wallet_file 1xxx # # default, good enough for most, takes about 5 hours # python tumbler.py wallet_file 1xxx # # for quick testing # python tumbler.py -N 2 1 -c 3 0.001 -l 0.1 -M 3 -a 0 wallet_file 1xxx 1yyy wallet = Wallet(wallet_file, max_mix_depth=options['mixdepthsrc'] + options['mixdepthcount']) sync_wallet(wallet, fast=options['fastsync']) jm_single().wait_for_commitments = 1 mcs = [IRCMessageChannel(c) for c in get_irc_mchannels()] mcc = MessageChannelCollection(mcs) log.info('starting tumbler') tumbler = Tumbler(mcc, wallet, tx_list, options) try: log.info('connecting to message channels') mcc.run() except: log.warn('Quitting! Dumping object contents to logfile.') debug_dump_object(wallet, ['addr_cache', 'keys', 'seed']) debug_dump_object(tumbler) debug_dump_object(tumbler.cjtx) import traceback log.debug(traceback.format_exc())
def test_tx_commitments_used(setup_podle, consume_tx, age_required, cmt_age): tries = jm_single().config.getint("POLICY", "taker_utxo_retries") #remember and reset at the end taker_utxo_age = jm_single().config.getint("POLICY", "taker_utxo_age") jm_single().config.set("POLICY", "taker_utxo_age", str(age_required)) #Don't want to wait too long, but must account for possible #throttling with !auth jm_single().maker_timeout_sec = 12 amount = 0 wallets = make_wallets(3, wallet_structures=[[1, 2, 1, 0, 0], [1, 2, 0, 0, 0], [2, 2, 1, 0, 0]], mean_amt=1) #the sendpayment bot uses the last wallet in the list wallet = wallets[2]['wallet'] #make_wallets calls grab_coins which mines 1 block per individual payout, #so the age of the coins depends on where they are in that list. The sendpayment #is the last wallet in the list, and we choose the non-tx utxos which are in #mixdepth 1 and 2 (2 and 1 utxos in each respectively). We filter for those #that have sufficient age, so to get 1 which is old enough, it will be the oldest, #which will have an age of 2 + 1 (the first utxo spent to that wallet). #So if we need an age of 6, we need to mine 3 more blocks. blocks_reqd = cmt_age - 3 jm_single().bc_interface.tick_forward_chain(blocks_reqd) yigen_procs = [] for i in range(2): ygp = local_command([python_cmd, yg_cmd,\ str(wallets[i]['seed'])], bg=True) time.sleep(2) #give it a chance yigen_procs.append(ygp) time.sleep(5) destaddr = btc.privkey_to_address(binascii.hexlify(os.urandom(32)), magicbyte=get_p2pk_vbyte()) addr_valid, errormsg = validate_address(destaddr) assert addr_valid, "Invalid destination address: " + destaddr + \ ", error message: " + errormsg log.debug('starting sendpayment') jm_single().bc_interface.sync_wallet(wallet) log.debug("Here is the whole wallet: \n" + str(wallet.unspent)) #Trigger PING LAG sending artificially joinmarket.irc.PING_INTERVAL = 3 mcs = [IRCMessageChannel(c) for c in get_irc_mchannels()] mcc = MessageChannelCollection(mcs) if consume_tx: #add all utxo in mixdepth 0 to 'used' list of commitments, utxos = wallet.get_utxos_by_mixdepth()[0] for u, addrval in utxos.iteritems(): priv = wallet.get_key_from_addr(addrval['address']) podle = btc.PoDLE(u, priv) for i in range(tries): #loop because we want to use up all retries of this utxo commitment = podle.generate_podle(i)['commit'] btc.update_commitments(commitment=commitment) #Now test a sendpayment from mixdepth 0 with all the depth 0 utxos #used up, so that the other utxos in the wallet get used. taker = sendpayment.SendPayment(mcc, wallet, destaddr, amount, 2, 5000, 3, 0, True, weighted_order_choose) try: log.debug('starting message channels') mcc.run() finally: if any(yigen_procs): for ygp in yigen_procs: #NB *GENTLE* shutdown is essential for #test coverage reporting! ygp.send_signal(signal.SIGINT) ygp.wait() #wait for block generation time.sleep(5) received = jm_single().bc_interface.get_received_by_addr( [destaddr], None)['data'][0]['balance'] jm_single().config.set("POLICY", "taker_utxo_age", str(taker_utxo_age)) if cmt_age < age_required: assert received == 0, "Coins arrived but shouldn't" else: assert received != 0, "sendpayment failed - coins not arrived, " +\ "received: " + str(received)
def test_external_commitment_used(setup_podle): tries = jm_single().config.getint("POLICY", "taker_utxo_retries") #Don't want to wait too long, but must account for possible #throttling with !auth jm_single().maker_timeout_sec = 12 amount = 50000000 wallets = make_wallets(3, wallet_structures=[[1, 0, 0, 0, 0], [1, 0, 0, 0, 0], [1, 1, 0, 0, 0]], mean_amt=1) #the sendpayment bot uses the last wallet in the list wallet = wallets[2]['wallet'] yigen_procs = [] for i in range(2): ygp = local_command([python_cmd, yg_cmd,\ str(wallets[i]['seed'])], bg=True) time.sleep(2) #give it a chance yigen_procs.append(ygp) #A significant delay is needed to wait for the yield generators to sync time.sleep(10) destaddr = btc.privkey_to_address(binascii.hexlify(os.urandom(32)), magicbyte=get_p2pk_vbyte()) addr_valid, errormsg = validate_address(destaddr) assert addr_valid, "Invalid destination address: " + destaddr + \ ", error message: " + errormsg log.debug('starting sendpayment') jm_single().bc_interface.sync_wallet(wallet) #Trigger PING LAG sending artificially joinmarket.irc.PING_INTERVAL = 3 mcs = [IRCMessageChannel(c) for c in get_irc_mchannels()] mcc = MessageChannelCollection(mcs) #add all utxo in mixdepth 0 to 'used' list of commitments, utxos = wallet.get_utxos_by_mixdepth()[0] for u, addrval in utxos.iteritems(): priv = wallet.get_key_from_addr(addrval['address']) podle = btc.PoDLE(u, priv) for i in range(tries): #loop because we want to use up all retries of this utxo commitment = podle.generate_podle(i)['commit'] btc.update_commitments(commitment=commitment) #create a new utxo, notionally from an external source; to make life a little #easier we'll pay to another mixdepth, but this is OK because #taker does not source from here currently, only from the utxos chosen #for the transaction, not the whole wallet. So we can treat it as if #external (don't access its privkey). utxos = wallet.get_utxos_by_mixdepth()[1] ecs = {} for u, addrval in utxos.iteritems(): priv = wallet.get_key_from_addr(addrval['address']) ecs[u] = {} ecs[u]['reveal'] = {} for j in range(tries): P, P2, s, e, commit = generate_single_podle_sig( binascii.unhexlify(priv), j) if 'P' not in ecs[u]: ecs[u]['P'] = P ecs[u]['reveal'][j] = {'P2': P2, 's': s, 'e': e} btc.update_commitments(external_to_add=ecs) #Now the conditions described above hold. We do a normal single #sendpayment. taker = sendpayment.SendPayment(mcc, wallet, destaddr, amount, 2, 5000, 3, 0, True, weighted_order_choose) try: log.debug('starting message channels') mcc.run() finally: if any(yigen_procs): for ygp in yigen_procs: #NB *GENTLE* shutdown is essential for #test coverage reporting! ygp.send_signal(signal.SIGINT) ygp.wait() #wait for block generation time.sleep(5) received = jm_single().bc_interface.get_received_by_addr( [destaddr], None)['data'][0]['balance'] assert received == amount, "sendpayment failed - coins not arrived, " +\ "received: " + str(received) #Cleanup - remove the external commitments added btc.update_commitments(external_to_remove=ecs)
def test_failed_sendpayment(setup_podle, num_ygs, wallet_structures, mean_amt, mixdepth, sending_amt): """Test of initiating joins, but failing to complete, to see commitment usage. YGs in background as per test_regtest. Use sweeps to avoid recover_from_nonrespondants without intruding into sendpayment code. """ makercount = num_ygs answeryes = True txfee = 5000 waittime = 3 #Don't want to wait too long, but must account for possible #throttling with !auth jm_single().maker_timeout_sec = 12 amount = 0 wallets = make_wallets(makercount + 1, wallet_structures=wallet_structures, mean_amt=mean_amt) #the sendpayment bot uses the last wallet in the list wallet = wallets[makercount]['wallet'] yigen_procs = [] for i in range(makercount): ygp = local_command([python_cmd, yg_cmd,\ str(wallets[i]['seed'])], bg=True) time.sleep(2) #give it a chance yigen_procs.append(ygp) #A significant delay is needed to wait for the yield generators to sync time.sleep(20) destaddr = btc.privkey_to_address(os.urandom(32), from_hex=False, magicbyte=get_p2pk_vbyte()) addr_valid, errormsg = validate_address(destaddr) assert addr_valid, "Invalid destination address: " + destaddr + \ ", error message: " + errormsg #TODO paramatetrize this as a test variable chooseOrdersFunc = weighted_order_choose log.debug('starting sendpayment') jm_single().bc_interface.sync_wallet(wallet) #Trigger PING LAG sending artificially joinmarket.irc.PING_INTERVAL = 3 mcs = [IRCMessageChannel(c) for c in get_irc_mchannels()] mcc = MessageChannelCollection(mcs) #Allow taker more retries than makers allow, so as to trigger #blacklist failure case jm_single().config.set("POLICY", "taker_utxo_retries", "4") #override ioauth receipt with a dummy do-nothing callback: def on_ioauth(*args): log.debug("Taker received: " + ','.join([str(x) for x in args])) class DummySendPayment(sendpayment.SendPayment): def __init__(self, msgchan, wallet, destaddr, amount, makercount, txfee, waittime, mixdepth, answeryes, chooseOrdersFunc, on_ioauth): self.on_ioauth = on_ioauth self.podle_fails = 0 self.podle_allowed_fails = 3 #arbitrary; but do it more than once self.retries = 0 super(DummySendPayment, self).__init__(msgchan, wallet, destaddr, amount, makercount, txfee, waittime, mixdepth, answeryes, chooseOrdersFunc) def on_welcome(self): Taker.on_welcome(self) DummyPaymentThread(self).start() class DummyPaymentThread(sendpayment.PaymentThread): def finishcallback(self, coinjointx): #Don't ignore makers and just re-start self.taker.retries += 1 if self.taker.podle_fails == self.taker.podle_allowed_fails: self.taker.msgchan.shutdown() return self.create_tx() def create_tx(self): try: super(DummyPaymentThread, self).create_tx() except btc.PoDLEError: log.debug("Got one commit failure, continuing") self.taker.podle_fails += 1 taker = DummySendPayment(mcc, wallet, destaddr, amount, makercount, txfee, waittime, mixdepth, answeryes, chooseOrdersFunc, on_ioauth) try: log.debug('starting message channels') mcc.run() finally: if any(yigen_procs): for ygp in yigen_procs: #NB *GENTLE* shutdown is essential for #test coverage reporting! ygp.send_signal(signal.SIGINT) ygp.wait() #We should have been able to try (tur -1) + podle_allowed_fails times assert taker.retries == jm_single().config.getint( "POLICY", "taker_utxo_retries") + taker.podle_allowed_fails #wait for block generation time.sleep(2) received = jm_single().bc_interface.get_received_by_addr( [destaddr], None)['data'][0]['balance'] #Sanity check no transaction succeeded assert received == 0
def main(): parser = OptionParser( usage= 'usage: %prog [options] [wallet file / fromaccount] [amount] [destaddr]', description='Sends a single payment from a given mixing depth of your ' + 'wallet to an given address using coinjoin and then switches off. Also sends from bitcoinqt. ' + 'Setting amount to zero will do a sweep, where the entire mix depth is emptied') parser.add_option('-f', '--txfee', action='store', type='int', dest='txfee', default=-1, help='number of satoshis per participant to use as the initial estimate '+ 'for the total transaction fee, default=dynamically estimated, note that this is adjusted '+ 'based on the estimated fee calculated after tx construction, based on '+ 'policy set in joinmarket.cfg.') parser.add_option( '-w', '--wait-time', action='store', type='float', dest='waittime', help='wait time in seconds to allow orders to arrive, default=15', default=15) parser.add_option('-N', '--makercount', action='store', type='int', dest='makercount', help='how many makers to coinjoin with, default random from 4 to 6', default=random.randint(4, 6)) parser.add_option( '-C', '--choose-cheapest', action='store_true', dest='choosecheapest', default=False, help='override weightened offers picking and choose cheapest. this might reduce anonymity.') parser.add_option( '-P', '--pick-orders', action='store_true', dest='pickorders', default=False, help= 'manually pick which orders to take. doesn\'t work while sweeping.') parser.add_option('-m', '--mixdepth', action='store', type='int', dest='mixdepth', help='mixing depth to spend from, default=0', default=0) parser.add_option('-a', '--amtmixdepths', action='store', type='int', dest='amtmixdepths', help='number of mixdepths in wallet, default 5', default=5) parser.add_option('-g', '--gap-limit', type="int", action='store', dest='gaplimit', help='gap limit for wallet, default=6', default=6) parser.add_option('--yes', action='store_true', dest='answeryes', default=False, help='answer yes to everything') parser.add_option( '--rpcwallet', action='store_true', dest='userpcwallet', default=False, help=('Use the Bitcoin Core wallet through json rpc, instead ' 'of the internal joinmarket wallet. Requires ' 'blockchain_source=json-rpc')) (options, args) = parser.parse_args() if len(args) < 3: parser.error('Needs a wallet, amount and destination address') sys.exit(0) wallet_name = args[0] amount = int(args[1]) destaddr = args[2] load_program_config() addr_valid, errormsg = validate_address(destaddr) if not addr_valid: print('ERROR: Address invalid. ' + errormsg) return chooseOrdersFunc = None if options.pickorders: chooseOrdersFunc = pick_order if amount == 0: print 'WARNING: You may have to pick offers multiple times' print 'WARNING: due to manual offer picking while sweeping' elif options.choosecheapest: chooseOrdersFunc = cheapest_order_choose else: # choose randomly (weighted) chooseOrdersFunc = weighted_order_choose # Dynamically estimate a realistic fee if it currently is the default value. # At this point we do not know even the number of our own inputs, so # we guess conservatively with 2 inputs and 2 outputs each if options.txfee == -1: options.txfee = max(options.txfee, estimate_tx_fee(2, 2)) log.debug("Estimated miner/tx fee for each cj participant: "+str(options.txfee)) assert(options.txfee >= 0) log.debug('starting sendpayment') if not options.userpcwallet: wallet = Wallet(wallet_name, options.amtmixdepths, options.gaplimit) else: wallet = BitcoinCoreWallet(fromaccount=wallet_name) jm_single().bc_interface.sync_wallet(wallet) mcs = [IRCMessageChannel(c) for c in get_irc_mchannels()] mcc = MessageChannelCollection(mcs) taker = SendPayment(mcc, wallet, destaddr, amount, options.makercount, options.txfee, options.waittime, options.mixdepth, options.answeryes, chooseOrdersFunc) try: log.debug('starting message channels') mcc.run() except: log.debug('CRASHING, DUMPING EVERYTHING') debug_dump_object(wallet, ['addr_cache', 'keys', 'wallet_name', 'seed']) debug_dump_object(taker) import traceback log.debug(traceback.format_exc())
def main(): parser = OptionParser( usage= 'usage: %prog [options] [cjamount] [cjaddr] [changeaddr] [utxos..]', description=('Creates an unsigned coinjoin transaction. Outputs ' 'a partially signed transaction hex string. The user ' 'must sign their inputs independently and broadcast ' 'them. The JoinMarket protocol requires the taker to ' 'have a single p2pk UTXO input to use to ' 'authenticate the encrypted messages. For this ' 'reason you must pass auth utxo and the ' 'corresponding private key')) # for cjamount=0 do a sweep, and ignore change address parser.add_option('-f', '--txfee', action='store', type='int', dest='txfee', default=10000, help='total miner fee in satoshis, default=10000') parser.add_option( '-w', '--wait-time', action='store', type='float', dest='waittime', help='wait time in seconds to allow orders to arrive, default=5', default=5) parser.add_option('-N', '--makercount', action='store', type='int', dest='makercount', help='how many makers to coinjoin with, default=2', default=2) parser.add_option( '-C', '--choose-cheapest', action='store_true', dest='choosecheapest', default=False, help='override weightened offers picking and choose cheapest') parser.add_option( '-P', '--pick-orders', action='store_true', dest='pickorders', default=False, help='manually pick which orders to take. doesn\'t work while sweeping.' ) parser.add_option('--yes', action='store_true', dest='answeryes', default=False, help='answer yes to everything') # TODO implement parser.add_option('-n', '--no-network', # action='store_true', dest='nonetwork', default=False, help='dont query # the blockchain interface, instead user must supply value of UTXOs on ' # + ' command line in the format txid:output/value-in-satoshi') (options, args) = parser.parse_args() if len(args) < 4: parser.error( 'Needs an amount, destination address, change address and utxos ') sys.exit(0) cjamount = int(args[0]) destaddr = args[1] changeaddr = args[2] cold_utxos = args[3:] load_program_config() addr_valid1, errormsg1 = validate_address(destaddr) errormsg2 = None # if amount = 0 dont bother checking changeaddr so user can write any junk if cjamount != 0: addr_valid2, errormsg2 = validate_address(changeaddr) else: addr_valid2 = True if not addr_valid1 or not addr_valid2: if not addr_valid1: print 'ERROR: Address invalid. ' + errormsg1 else: print 'ERROR: Address invalid. ' + errormsg2 return query_result = jm_single().bc_interface.query_utxo_set(cold_utxos) if None in query_result: print query_result utxo_data = {} for utxo, data in zip(cold_utxos, query_result): utxo_data[utxo] = {'address': data['address'], 'value': data['value']} print("Got this utxo data: " + str(utxo_data)) if options.pickorders and cjamount != 0: # cant use for sweeping chooseOrdersFunc = pick_order elif options.choosecheapest: chooseOrdersFunc = cheapest_order_choose else: # choose randomly (weighted) chooseOrdersFunc = weighted_order_choose log.debug('starting sendpayment') wallet = AbstractWallet() wallet.unspent = None mcs = [ IRCMessageChannel(c, jm_single().nickname) for c in get_irc_mchannels() ] mcc = MessageChannelCollection(mcs) taker = CreateUnsignedTx(mcc, wallet, cjamount, destaddr, changeaddr, utxo_data, options, chooseOrdersFunc) try: log.debug('starting message channels') mcc.run() except: log.debug('CRASHING, DUMPING EVERYTHING') debug_dump_object(wallet, ['addr_cache', 'keys', 'wallet_name', 'seed']) debug_dump_object(taker) import traceback log.debug(traceback.format_exc())
def test_sendpayment(setup_regtest, num_ygs, wallet_structures, mean_amt, mixdepth, sending_amt, ygcfs, fails, donate, rpcwallet): """Test of sendpayment code, with yield generators in background. """ log = get_log() makercount = num_ygs answeryes = True txfee = 5000 waittime = 5 amount = sending_amt wallets = make_wallets(makercount + 1, wallet_structures=wallet_structures, mean_amt=mean_amt) #the sendpayment bot uses the last wallet in the list if not rpcwallet: wallet = wallets[makercount]['wallet'] else: wallet = BitcoinCoreWallet(fromaccount="") yigen_procs = [] if ygcfs: assert makercount == len(ygcfs) for i in range(makercount): if ygcfs: #back up default config, overwrite before start os.rename("joinmarket.cfg", "joinmarket.cfg.bak") shutil.copy2(ygcfs[i], "joinmarket.cfg") ygp = local_command([python_cmd, yg_cmd,\ str(wallets[i]['seed'])], bg=True) time.sleep(2) #give it a chance yigen_procs.append(ygp) if ygcfs: #Note: in case of using multiple configs, #the starting config is what is used by sendpayment os.rename("joinmarket.cfg.bak", "joinmarket.cfg") #A significant delay is needed to wait for the yield generators to sync time.sleep(20) if donate: destaddr = None else: destaddr = btc.privkey_to_address( os.urandom(32), from_hex=False, magicbyte=get_p2pk_vbyte()) addr_valid, errormsg = validate_address(destaddr) assert addr_valid, "Invalid destination address: " + destaddr + \ ", error message: " + errormsg #TODO paramatetrize this as a test variable chooseOrdersFunc = weighted_order_choose log.debug('starting sendpayment') sync_wallet(wallet) #Trigger PING LAG sending artificially joinmarket.irc.PING_INTERVAL = 3 mcs = [IRCMessageChannel(c) for c in get_irc_mchannels()] mcc = MessageChannelCollection(mcs) #hack fix for #356 if multiple orders per counterparty #removed for now. #if amount==0: makercount=2 taker = sendpayment.SendPayment(mcc, wallet, destaddr, amount, makercount-2, txfee, waittime, mixdepth, answeryes, chooseOrdersFunc) try: log.debug('starting message channels') mcc.run(failures=fails) finally: if any(yigen_procs): for ygp in yigen_procs: #NB *GENTLE* shutdown is essential for #test coverage reporting! ygp.send_signal(signal.SIGINT) ygp.wait() #wait for block generation time.sleep(5) if not donate: received = jm_single().bc_interface.get_received_by_addr( [destaddr], None)['data'][0]['balance'] if amount != 0: assert received == amount, "sendpayment failed - coins not arrived, " +\ "received: " + str(received) #TODO: how to check success for sweep case? else: assert received != 0
def test_tumbler(setup_tumbler, num_ygs, wallet_structures, mean_amt, sdev_amt, yg_excess): """Test of tumbler code, with yield generators in background. """ log = get_log() options = Options() options.mixdepthsrc = 0 options.mixdepthcount = 4 options.minmakercount = 2 options.makercountrange = (num_ygs, 0) options.maxcjfee = (0.01, 10000) options.txfee = 5000 options.addrcount = 3 options.donateamount = 0.5 options.txcountparams = (4, 1) options.mintxcount = 1 options.amountpower = 100 options.timelambda = 0.2 options.waittime = 10 options.mincjamount = 1000000 options.liquiditywait = 5 options.maxbroadcasts = 4 options.maxcreatetx = 9 options = vars(options) wallets = make_wallets(num_ygs + 1, wallet_structures=wallet_structures, mean_amt=mean_amt, sdev_amt=sdev_amt) #need to make sure that at least some ygs have substantially #more coins for last stages of sweep/spend in tumble: for i in range(num_ygs): jm_single().bc_interface.grab_coins( wallets[i]['wallet'].get_external_addr(0), yg_excess) #the tumbler bot uses the last wallet in the list wallet = wallets[num_ygs]['wallet'] yigen_procs = [] for i in range(num_ygs): ygp = local_command([python_cmd, yg_cmd,\ str(wallets[i]['seed'])], bg=True) time.sleep(2) #give it a chance yigen_procs.append(ygp) #A significant delay is needed to wait for the yield generators to sync time.sleep(20) destaddrs = [] for i in range(3): destaddr = btc.privkey_to_address(os.urandom(32), from_hex=False, magicbyte=get_p2pk_vbyte()) addr_valid, errormsg = validate_address(destaddr) assert addr_valid, "Invalid destination address: " + destaddr + \ ", error message: " + errormsg destaddrs.append(destaddr) tx_list = tumbler.generate_tumbler_tx(destaddrs, options) pprint(tx_list) if options['addrcount'] + 1 > options['mixdepthcount']: print( 'not enough mixing depths to pay to all destination addresses, ' 'increasing mixdepthcount') options['mixdepthcount'] = options['addrcount'] + 1 tx_list2 = copy.deepcopy(tx_list) tx_dict = {} for tx in tx_list2: srcmixdepth = tx['srcmixdepth'] tx.pop('srcmixdepth') if srcmixdepth not in tx_dict: tx_dict[srcmixdepth] = [] tx_dict[srcmixdepth].append(tx) dbg_tx_list = [] for srcmixdepth, txlist in tx_dict.iteritems(): dbg_tx_list.append({'srcmixdepth': srcmixdepth, 'tx': txlist}) log.debug('tumbler transaction list') pprint(dbg_tx_list) total_wait = sum([tx['wait'] for tx in tx_list]) print('creates ' + str(len(tx_list)) + ' transactions in total') print('waits in total for ' + str(len(tx_list)) + ' blocks and ' + str(total_wait) + ' minutes') total_block_and_wait = len(tx_list) * 10 + total_wait print('estimated time taken ' + str(total_block_and_wait) + ' minutes or ' + str(round(total_block_and_wait / 60.0, 2)) + ' hours') jm_single().nickname = random_nick() log.debug('starting tumbler') jm_single().bc_interface.sync_wallet(wallet) jm_single().bc_interface.pushtx_failure_prob = 0.4 mcs = [ IRCMessageChannel(c, jm_single().nickname) for c in get_irc_mchannels() ] mcc = MessageChannelCollection(mcs) tumbler_bot = tumbler.Tumbler(mcc, wallet, tx_list, options) try: log.debug('starting message channels') mcc.run() except: log.debug('CRASHING, DUMPING EVERYTHING') debug_dump_object(wallet, ['addr_cache', 'keys', 'wallet_name', 'seed']) debug_dump_object(tumbler_bot) import traceback log.debug(traceback.format_exc()) finally: if any(yigen_procs): for ygp in yigen_procs: #NB *GENTLE* shutdown is essential for #test coverage reporting! ygp.send_signal(signal.SIGINT) ygp.wait() #wait for block generation time.sleep(5) received = jm_single().bc_interface.get_received_by_addr( [destaddr], None)['data'][0]['balance'] assert received != 0 """TODO: figure out a sensible assertion check for the destination
def ygmain(ygclass, txfee=1000, cjfee_a=200, cjfee_r=0.002, ordertype='reloffer', nickserv_password='', minsize=100000, mix_levels=5): import sys parser = OptionParser(usage='usage: %prog [options] [wallet file]') parser.add_option('-o', '--ordertype', action='store', type='string', dest='ordertype', default=ordertype, help='type of order; can be either reloffer or absoffer') parser.add_option('-t', '--txfee', action='store', type='int', dest='txfee', default=txfee, help='minimum miner fee in satoshis') parser.add_option('-c', '--cjfee', action='store', type='string', dest='cjfee', default='', help='requested coinjoin fee in satoshis or proportion') parser.add_option('-p', '--password', action='store', type='string', dest='password', default=nickserv_password, help='irc nickserv password') parser.add_option('-s', '--minsize', action='store', type='int', dest='minsize', default=minsize, help='minimum coinjoin size in satoshis') parser.add_option('-m', '--mixlevels', action='store', type='int', dest='mixlevels', default=mix_levels, help='number of mixdepths to use') (options, args) = parser.parse_args() if len(args) < 1: parser.error('Needs a wallet') sys.exit(0) seed = args[0] ordertype = options.ordertype txfee = options.txfee if ordertype == 'reloffer': if options.cjfee != '': cjfee_r = options.cjfee # minimum size is such that you always net profit at least 20% #of the miner fee minsize = max(int(1.2 * txfee / float(cjfee_r)), options.minsize) elif ordertype == 'absoffer': if options.cjfee != '': cjfee_a = int(options.cjfee) minsize = options.minsize else: parser.error('You specified an incorrect order type which ' +\ 'can be either reloffer or absoffer') sys.exit(0) nickserv_password = options.password mix_levels = options.mixlevels load_program_config() if isinstance(jm_single().bc_interface, BlockrInterface): c = ('\nYou are running a yield generator by polling the blockr.io ' 'website. This is quite bad for privacy. That site is owned by ' 'coinbase.com Also your bot will run faster and more efficently, ' 'you can be immediately notified of new bitcoin network ' 'information so your money will be working for you as hard as ' 'possibleLearn how to setup JoinMarket with Bitcoin Core: ' 'https://github.com/chris-belcher/joinmarket/wiki/Running' '-JoinMarket-with-Bitcoin-Core-full-node') print(c) ret = raw_input('\nContinue? (y/n):') if ret[0] != 'y': return wallet = Wallet(seed, max_mix_depth=mix_levels) jm_single().bc_interface.sync_wallet(wallet) log.debug('starting yield generator') mcs = [IRCMessageChannel(c, realname='btcint=' + jm_single().config.get( "BLOCKCHAIN", "blockchain_source"), password=nickserv_password) for c in get_irc_mchannels()] mcc = MessageChannelCollection(mcs) maker = ygclass(mcc, wallet, [options.txfee, cjfee_a, cjfee_r, options.ordertype, options.minsize, mix_levels]) try: log.debug('connecting to message channels') mcc.run() except: log.debug('CRASHING, DUMPING EVERYTHING') debug_dump_object(wallet, ['addr_cache', 'keys', 'seed']) debug_dump_object(maker) debug_dump_object(mcc) import traceback log.debug(traceback.format_exc())
def main(): parser = OptionParser( usage= 'usage: %prog [options] [wallet file] [[dest..] [amount]..]', description='Sends a payment from your wallet to an given address' + ' using coinjoin but for users who dont mind ' + 'waiting. First acts as a maker, announcing an order' + ' and waiting for someone to fill it. After a set ' + 'period of time, gives up waiting and acts as a taker' + ' and coinjoins any remaining coins. Is able to send' + ' to multiple locations one after another. [dest] ' + 'can be multiple addresses or a xpub BIP32 key. xpub' + ' keys can be optionally followed with :index to ' + 'start from another address than zero') parser.add_option( '-f', '--txfee', action='store', type='int', dest='txfee', default=1000, help='miner fee contribution, in satoshis, default=1000') parser.add_option( '-N', '--makercount', action='store', type='int', dest='makercount', help='how many makers to coinjoin with, default random ' 'from 5 to 7', default=random.randint(5, 7)) parser.add_option( '-w', '--wait-time', action='store', type='float', dest='waittime', help='wait time in hours as a maker before becoming a taker, ' + 'or zero to wait forever, default=8', default=8) parser.add_option( '-c', '--base-cjfee', action='store', type='int', dest='cjfee_base', help= 'base coinjoin fee asked for when being a maker, in satoshis per' + ' order filled, default=500', default=500) parser.add_option( '-a', '--add-cjfee', action='store', type='int', dest='cjfee_add', help= 'additional coinjoin fee asked for when being a maker when ' + 'coinjoin amount not exact, in satoshis per order filled' + ', default=1000', default=1000) parser.add_option( '-m', '--mixdepth', action='store', type='int', dest='mixdepth', help='mixing depth to spend from, default=0', default=0) parser.add_option( '--rpcwallet', action='store_true', dest='userpcwallet', default=False, help= 'Use the Bitcoin Core wallet through json rpc, instead of the ' + 'internal joinmarket wallet. Requires blockchain_source=json-rpc.' + ' NOT IMPLEMENTED YET') parser.add_option('--fast', action='store_true', dest='fastsync', default=False, help=('choose to do fast wallet sync, only for Core and ' 'only for previously synced wallet')) parser.add_option( '-x', '--maxcjfee', type='float', dest='maxcjfee', nargs=2, default=(0.01, 10000), help='maximum coinjoin fee and bitcoin value the taker is ' + 'willing to pay to a single market maker. Both values need' + ' to be exceeded, so if the fee is 30% but only 500satoshi ' + 'is paid the tx will go ahead. default=0.01, 10000 ' + '(1%, 10000satoshi)') parser.add_option( '-q', '--liquiditywait', type='int', dest='liquiditywait', default=20, help= 'amount of seconds to wait after failing to choose suitable orders' ' before trying again, default 20') parser.add_option( '-u', '--minoutputsize', type='int', dest='minoutputsize', nargs=1, default=30000, help='minimum size of output in satoshis produced by ' 'patientsendpayment. default=30000 satoshi') (options, args) = parser.parse_args() if len(args) < 3: parser.error('Needs a wallet, amount and destination address') sys.exit(0) wallet_name = args[0] load_program_config() send_jobs = [] destination = None for ar in args[1:]: if ar.isdigit(): if destination == None: log.error('found amount without destination') return elif isinstance(destination, list): send_jobs.append( {'amount': int(ar), 'addresses': destination, 'index': 0} ) elif isinstance(destination, tuple): send_jobs.append( {'amount': int(ar), 'xpub': destination[0] , 'index': destination[1]} ) else: assert False destination = None else: if validate_address(ar)[0]: if destination == None: destination = [] destination.append(ar) else: index = 0 colon = ar.find(':') if colon > -1: index = int(ar[colon+1:]) ar = ar[:colon] if is_bip32_pubkey(ar): destination = (ar, index) else: log.error('unable to parse destination: ' + ar) return if destination != None: log.error('missing amount') return for j in send_jobs: print('sending ' + str(j['amount']) + ' satoshi to: ') if 'addresses' in j: for a in j['addresses']: print(' ' + get_next_address(j)) else: print(' ' + j['xpub'] + '\n starting from index: ' + str(j['index']) + '. first 5 addresses:') index_cache = j['index'] for i in range(5): print(' ' + get_next_address(j)) j['index'] = index_cache waittime = timedelta(hours=options.waittime).total_seconds() # todo: this section doesn't make a lot of sense if not options.userpcwallet: wallet = Wallet(wallet_name, options.mixdepth + 1) else: print 'not implemented yet' sys.exit(0) # wallet = BitcoinCoreWallet(fromaccount=wallet_name) sync_wallet(wallet, fast=options.fastsync) available_balance = wallet.get_balance_by_mixdepth()[options.mixdepth] total_amount = sum((j['amount'] for j in send_jobs)) if available_balance < total_amount: print 'not enough money at mixdepth=%d, exiting' % options.mixdepth return log.info('Running patient sender of a payment') mcs = [IRCMessageChannel(c) for c in get_irc_mchannels()] mcc = MessageChannelCollection(mcs) PatientSendPayment(mcc, wallet, send_jobs, options, waittime) try: mcc.run() except: log.warn('CRASHING, DUMPING EVERYTHING') debug_dump_object(wallet, ['addr_cache', 'keys', 'seed']) # todo: looks wrong. dump on the class object? # debug_dump_object(taker) import traceback traceback.print_exc()
def main(): parser = OptionParser( usage= 'usage: %prog [options] [wallet file / fromaccount] [amount] [destaddr]', description='Sends a single payment from a given mixing depth of your ' + 'wallet to an given address using coinjoin and then switches off. Also sends from bitcoinqt. ' + 'Setting amount to zero will do a sweep, where the entire mix depth is emptied' ) parser.add_option( '-f', '--txfee', action='store', type='int', dest='txfee', default=-1, help= 'number of satoshis per participant to use as the initial estimate ' + 'for the total transaction fee, default=dynamically estimated, note that this is adjusted ' + 'based on the estimated fee calculated after tx construction, based on ' + 'policy set in joinmarket.cfg.') parser.add_option( '-w', '--wait-time', action='store', type='float', dest='waittime', help='wait time in seconds to allow orders to arrive, default=15', default=15) parser.add_option('-N', '--makercount', action='store', type='int', dest='makercount', help='how many makers to coinjoin with, default random ' 'from 5 to 7; use 0 to send *direct* to a destination ' 'address, not using Joinmarket', default=random.randint(5, 7)) parser.add_option( '-C', '--choose-cheapest', action='store_true', dest='choosecheapest', default=False, help= 'override weightened offers picking and choose cheapest. this might reduce anonymity.' ) parser.add_option( '-P', '--pick-orders', action='store_true', dest='pickorders', default=False, help='manually pick which orders to take. doesn\'t work while sweeping.' ) parser.add_option('-m', '--mixdepth', action='store', type='int', dest='mixdepth', help='mixing depth to spend from, default=0', default=0) parser.add_option('-a', '--amtmixdepths', action='store', type='int', dest='amtmixdepths', help='number of mixdepths in wallet, default 5', default=5) parser.add_option('-g', '--gap-limit', type="int", action='store', dest='gaplimit', help='gap limit for wallet, default=6', default=6) parser.add_option('--yes', action='store_true', dest='answeryes', default=False, help='answer yes to everything') parser.add_option( '--rpcwallet', action='store_true', dest='userpcwallet', default=False, help=('Use the Bitcoin Core wallet through json rpc, instead ' 'of the internal joinmarket wallet. Requires ' 'blockchain_source=json-rpc')) parser.add_option('--fast', action='store_true', dest='fastsync', default=False, help=('choose to do fast wallet sync, only for Core and ' 'only for previously synced wallet')) (options, args) = parser.parse_args() if len(args) < 3: parser.error('Needs a wallet, amount and destination address') sys.exit(0) wallet_name = args[0] amount = int(args[1]) destaddr = args[2] load_program_config() addr_valid, errormsg = validate_address(destaddr) if not addr_valid: print('ERROR: Address invalid. ' + errormsg) return chooseOrdersFunc = None if options.pickorders: chooseOrdersFunc = pick_order if amount == 0: print 'WARNING: You may have to pick offers multiple times' print 'WARNING: due to manual offer picking while sweeping' elif options.choosecheapest: chooseOrdersFunc = cheapest_order_choose else: # choose randomly (weighted) chooseOrdersFunc = weighted_order_choose # Dynamically estimate a realistic fee if it currently is the default value. # At this point we do not know even the number of our own inputs, so # we guess conservatively with 2 inputs and 2 outputs each if options.txfee == -1: options.txfee = max(options.txfee, estimate_tx_fee(2, 2)) log.info("Estimated miner/tx fee for each cj participant: " + str(options.txfee)) assert (options.txfee >= 0) log.info('starting sendpayment') #If we are not direct sending, then minimum_maker setting should #not be larger than the requested number of counterparties if options.makercount != 0 and options.makercount < jm_single( ).config.getint("POLICY", "minimum_makers"): log.error("You selected a number of counterparties (" + \ str(options.makercount) + \ ") less than the " "minimum requirement (" + \ str(jm_single().config.getint("POLICY","minimum_makers")) + \ "); you can edit the value 'minimum_makers'" " in the POLICY section in joinmarket.cfg to correct this. " "Quitting.") exit(0) if not options.userpcwallet: max_mix_depth = max([options.mixdepth, options.amtmixdepths]) wallet = Wallet(wallet_name, max_mix_depth, options.gaplimit) else: wallet = BitcoinCoreWallet(fromaccount=wallet_name) sync_wallet(wallet, fast=options.fastsync) if options.makercount == 0: direct_send(wallet, amount, options.mixdepth, destaddr) return mcs = [IRCMessageChannel(c) for c in get_irc_mchannels()] mcc = MessageChannelCollection(mcs) log.info("starting sendpayment") taker = SendPayment(mcc, wallet, destaddr, amount, options.makercount, options.txfee, options.waittime, options.mixdepth, options.answeryes, chooseOrdersFunc) try: log.info('starting message channels') mcc.run() except: log.warn('Quitting! Dumping object contents to logfile.') debug_dump_object(wallet, ['addr_cache', 'keys', 'wallet_name', 'seed']) debug_dump_object(taker) import traceback log.debug(traceback.format_exc())
def test_sendpayment(setup_regtest, num_ygs, wallet_structures, mean_amt, mixdepth, sending_amt, ygcfs, fails, donate): """Test of sendpayment code, with yield generators in background. """ log = get_log() makercount = num_ygs answeryes = True txfee = 5000 waittime = 5 amount = sending_amt wallets = make_wallets(makercount + 1, wallet_structures=wallet_structures, mean_amt=mean_amt) #the sendpayment bot uses the last wallet in the list wallet = wallets[makercount]['wallet'] yigen_procs = [] if ygcfs: assert makercount == len(ygcfs) for i in range(makercount): if ygcfs: #back up default config, overwrite before start os.rename("joinmarket.cfg", "joinmarket.cfg.bak") shutil.copy2(ygcfs[i], "joinmarket.cfg") ygp = local_command([python_cmd, yg_cmd,\ str(wallets[i]['seed'])], bg=True) time.sleep(2) #give it a chance yigen_procs.append(ygp) if ygcfs: #Note: in case of using multiple configs, #the starting config is what is used by sendpayment os.rename("joinmarket.cfg.bak", "joinmarket.cfg") #A significant delay is needed to wait for the yield generators to sync time.sleep(20) if donate: destaddr = None else: destaddr = btc.privkey_to_address(os.urandom(32), from_hex=False, magicbyte=get_p2pk_vbyte()) addr_valid, errormsg = validate_address(destaddr) assert addr_valid, "Invalid destination address: " + destaddr + \ ", error message: " + errormsg #TODO paramatetrize this as a test variable chooseOrdersFunc = weighted_order_choose log.debug('starting sendpayment') jm_single().bc_interface.sync_wallet(wallet) #Trigger PING LAG sending artificially joinmarket.irc.PING_INTERVAL = 3 mcs = [IRCMessageChannel(c) for c in get_irc_mchannels()] mcc = MessageChannelCollection(mcs) #hack fix for #356 if multiple orders per counterparty #removed for now. #if amount==0: makercount=2 taker = sendpayment.SendPayment(mcc, wallet, destaddr, amount, makercount - 2, txfee, waittime, mixdepth, answeryes, chooseOrdersFunc) try: log.debug('starting message channels') mcc.run(failures=fails) finally: if any(yigen_procs): for ygp in yigen_procs: #NB *GENTLE* shutdown is essential for #test coverage reporting! ygp.send_signal(signal.SIGINT) ygp.wait() #wait for block generation time.sleep(5) if not donate: received = jm_single().bc_interface.get_received_by_addr( [destaddr], None)['data'][0]['balance'] if amount != 0: assert received == amount, "sendpayment failed - coins not arrived, " +\ "received: " + str(received) #TODO: how to check success for sweep case? else: assert received != 0
def ygmain(ygclass, txfee=1000, cjfee_a=200, cjfee_r=0.002, ordertype='reloffer', nickserv_password='', minsize=100000, mix_levels=5, gaplimit=6): import sys parser = OptionParser(usage='usage: %prog [options] [wallet file]') parser.add_option('-o', '--ordertype', action='store', type='string', dest='ordertype', default=ordertype, help='type of order; can be either reloffer or absoffer') parser.add_option('-t', '--txfee', action='store', type='int', dest='txfee', default=txfee, help='minimum miner fee in satoshis') parser.add_option('-c', '--cjfee', action='store', type='string', dest='cjfee', default='', help='requested coinjoin fee in satoshis or proportion') parser.add_option('-p', '--password', action='store', type='string', dest='password', default=nickserv_password, help='irc nickserv password') parser.add_option('-s', '--minsize', action='store', type='int', dest='minsize', default=minsize, help='minimum coinjoin size in satoshis') parser.add_option('-m', '--mixlevels', action='store', type='int', dest='mixlevels', default=mix_levels, help='number of mixdepths to use') parser.add_option('-g', '--gap-limit', action='store', type="int", dest='gaplimit', default=6, help='gap limit for wallet, default=6') parser.add_option('--fast', action='store_true', dest='fastsync', default=False, help=('choose to do fast wallet sync, only for Core and ' 'only for previously synced wallet')) (options, args) = parser.parse_args() if len(args) < 1: parser.error('Needs a wallet') sys.exit(0) seed = args[0] ordertype = options.ordertype txfee = options.txfee if ordertype == 'reloffer': if options.cjfee != '': cjfee_r = options.cjfee # minimum size is such that you always net profit at least 20% #of the miner fee minsize = max(int(1.2 * txfee / float(cjfee_r)), options.minsize) elif ordertype == 'absoffer': if options.cjfee != '': cjfee_a = int(options.cjfee) minsize = options.minsize else: parser.error('You specified an incorrect order type which ' +\ 'can be either reloffer or absoffer') sys.exit(0) nickserv_password = options.password mix_levels = options.mixlevels load_program_config() if isinstance(jm_single().bc_interface, BlockrInterface): c = ('\nYou are running a yield generator by polling the blockr.io ' 'website. This is quite bad for privacy. That site is owned by ' 'coinbase.com Also your bot will run faster and more efficently, ' 'you can be immediately notified of new bitcoin network ' 'information so your money will be working for you as hard as ' 'possibleLearn how to setup JoinMarket with Bitcoin Core: ' 'https://github.com/chris-belcher/joinmarket/wiki/Running' '-JoinMarket-with-Bitcoin-Core-full-node') print(c) ret = raw_input('\nContinue? (y/n):') if ret[0] != 'y': return wallet = Wallet(seed, max_mix_depth=mix_levels, gaplimit=gaplimit) sync_wallet(wallet, fast=options.fastsync) mcs = [ IRCMessageChannel( c, realname='btcint=' + jm_single().config.get("BLOCKCHAIN", "blockchain_source"), password=nickserv_password) for c in get_irc_mchannels() ] mcc = MessageChannelCollection(mcs) log.info('starting yield generator') maker = ygclass(mcc, wallet, [ options.txfee, cjfee_a, cjfee_r, options.ordertype, options.minsize, mix_levels ]) try: log.info('connecting to message channels') mcc.run() except: log.warn('Quitting! Dumping object contents to logfile.') debug_dump_object(wallet, ['addr_cache', 'keys', 'seed']) debug_dump_object(maker) debug_dump_object(mcc, ['nick_priv', 'nick_pkh_raw']) import traceback log.debug(traceback.format_exc())
def test_tumbler(setup_tumbler, num_ygs, wallet_structures, mean_amt, sdev_amt, yg_excess): """Test of tumbler code, with yield generators in background. """ log = get_log() options = Options() options.mixdepthsrc = 0 options.mixdepthcount = 4 options.minmakercount = 2 options.makercountrange = (num_ygs, 0) options.maxcjfee = (0.01, 10000) options.txfee = 5000 options.addrcount = 3 options.donateamount = 0.5 options.txcountparams = (4, 1) options.mintxcount = 1 options.amountpower = 100 options.timelambda = 0.2 options.waittime = 10 options.mincjamount = 1000000 options.liquiditywait = 5 options.maxbroadcasts = 4 options.maxcreatetx = 9 options = vars(options) wallets = make_wallets(num_ygs + 1, wallet_structures=wallet_structures, mean_amt=mean_amt, sdev_amt=sdev_amt) #need to make sure that at least some ygs have substantially #more coins for last stages of sweep/spend in tumble: for i in range(num_ygs): jm_single().bc_interface.grab_coins( wallets[i]['wallet'].get_external_addr(0), yg_excess) #the tumbler bot uses the last wallet in the list wallet = wallets[num_ygs]['wallet'] yigen_procs = [] for i in range(num_ygs): ygp = local_command([python_cmd, yg_cmd,\ str(wallets[i]['seed'])], bg=True) time.sleep(2) #give it a chance yigen_procs.append(ygp) #A significant delay is needed to wait for the yield generators to sync time.sleep(20) destaddrs = [] for i in range(3): destaddr = btc.privkey_to_address( os.urandom(32), from_hex=False, magicbyte=get_p2pk_vbyte()) addr_valid, errormsg = validate_address(destaddr) assert addr_valid, "Invalid destination address: " + destaddr + \ ", error message: " + errormsg destaddrs.append(destaddr) tx_list = tumbler.generate_tumbler_tx(destaddrs, options) pprint(tx_list) if options['addrcount'] + 1 > options['mixdepthcount']: print('not enough mixing depths to pay to all destination addresses, ' 'increasing mixdepthcount') options['mixdepthcount'] = options['addrcount'] + 1 tx_list2 = copy.deepcopy(tx_list) tx_dict = {} for tx in tx_list2: srcmixdepth = tx['srcmixdepth'] tx.pop('srcmixdepth') if srcmixdepth not in tx_dict: tx_dict[srcmixdepth] = [] tx_dict[srcmixdepth].append(tx) dbg_tx_list = [] for srcmixdepth, txlist in tx_dict.iteritems(): dbg_tx_list.append({'srcmixdepth': srcmixdepth, 'tx': txlist}) log.debug('tumbler transaction list') pprint(dbg_tx_list) total_wait = sum([tx['wait'] for tx in tx_list]) print('creates ' + str(len(tx_list)) + ' transactions in total') print('waits in total for ' + str(len(tx_list)) + ' blocks and ' + str( total_wait) + ' minutes') total_block_and_wait = len(tx_list) * 10 + total_wait print('estimated time taken ' + str(total_block_and_wait) + ' minutes or ' + str(round(total_block_and_wait / 60.0, 2)) + ' hours') jm_single().nickname = random_nick() log.debug('starting tumbler') jm_single().bc_interface.sync_wallet(wallet) jm_single().bc_interface.pushtx_failure_prob = 0.4 mcs = [IRCMessageChannel(c, jm_single().nickname) for c in get_irc_mchannels()] mcc = MessageChannelCollection(mcs) tumbler_bot = tumbler.Tumbler(mcc, wallet, tx_list, options) try: log.debug('starting message channels') mcc.run() except: log.debug('CRASHING, DUMPING EVERYTHING') debug_dump_object(wallet, ['addr_cache', 'keys', 'wallet_name', 'seed']) debug_dump_object(tumbler_bot) import traceback log.debug(traceback.format_exc()) finally: if any(yigen_procs): for ygp in yigen_procs: #NB *GENTLE* shutdown is essential for #test coverage reporting! ygp.send_signal(signal.SIGINT) ygp.wait() #wait for block generation time.sleep(5) received = jm_single().bc_interface.get_received_by_addr( [destaddr], None)['data'][0]['balance'] assert received != 0 """TODO: figure out a sensible assertion check for the destination
def main(): parser = OptionParser( usage='usage: %prog [options] [wallet file] [destaddr(s)...]', description= 'Sends bitcoins to many different addresses using coinjoin in' ' an attempt to break the link between them. Sending to multiple ' ' addresses is highly recommended for privacy. This tumbler can' ' 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 spend from. Useful if a previous tumbler run prematurely ended with ' + 'coins being left in higher mixing levels, this option can be used to resume without needing' + ' to send to another address. default=0', default=0) parser.add_option( '-f', '--txfee', action='store', type='int', dest='txfee', default=-1, help='number of satoshis per participant to use as the initial estimate '+ 'for the total transaction fee, default=dynamically estimated, note that this is adjusted '+ 'based on the estimated fee calculated after tx construction, based on '+ 'policy set in joinmarket.cfg.') parser.add_option( '-a', '--addrcount', type='int', dest='addrcount', default=3, help= 'How many destination addresses in total should be used. If not enough are given' ' as command line arguments, the script will ask for more. This parameter is required' ' to stop amount correlation. default=3') parser.add_option( '-x', '--maxcjfee', type='float', dest='maxcjfee', nargs=2, default=(0.01, 10000), help='maximum coinjoin fee and bitcoin value the tumbler is ' 'willing to pay to a single market maker. Both values need to be exceeded, so if ' 'the fee is 30% but only 500satoshi is paid the tx will go ahead. default=0.01, 10000 (1%, 10000satoshi)') parser.add_option( '-N', '--makercountrange', type='float', nargs=2, action='store', dest='makercountrange', help= 'Input the mean and spread of number of makers to use. e.g. 5 1.5 will be a normal distribution ' 'with mean 5 and standard deveation 1.5 inclusive, default=5 1.5', default=(5, 1.5)) parser.add_option( '--minmakercount', type='int', dest='minmakercount', default=3, help= 'The minimum maker count in a transaction, random values below this are clamped at this number. default=3') parser.add_option( '-M', '--mixdepthcount', type='int', dest='mixdepthcount', help='How many mixing depths to mix through', default=4) parser.add_option( '-c', '--txcountparams', type='float', nargs=2, dest='txcountparams', default=(4, 1), help= 'The number of transactions to take coins from one mixing depth to the next, it is' ' randomly chosen following a normal distribution. Should be similar to --addrask. ' 'This option controls the parameters of the normal distribution curve. (mean, standard deviation). default=(4, 1)') parser.add_option( '--mintxcount', type='int', dest='mintxcount', default=1, help='The minimum transaction count per mixing level, default=1') parser.add_option( '--donateamount', type='float', dest='donateamount', default=0, help= 'percent of funds to donate to joinmarket development, or zero to opt out (default=0%)') parser.add_option( '--amountpower', type='float', dest='amountpower', default=100.0, help= 'The output amounts follow a power law distribution, this is the power, default=100.0') parser.add_option( '-l', '--timelambda', type='float', dest='timelambda', default=30, help= 'Average the number of minutes to wait between transactions. Randomly chosen ' ' following an exponential distribution, which describes the time between uncorrelated' ' events. default=30') parser.add_option( '-w', '--wait-time', action='store', type='float', dest='waittime', help='wait time in seconds to allow orders to arrive, default=20', default=20) parser.add_option( '-s', '--mincjamount', type='int', dest='mincjamount', default=100000, help='minimum coinjoin amount in transaction in satoshi, default 100k') parser.add_option( '-q', '--liquiditywait', type='int', dest='liquiditywait', default=60, help= 'amount of seconds to wait after failing to choose suitable orders before trying again, default 60') parser.add_option( '--maxbroadcasts', type='int', dest='maxbroadcasts', default=4, help= 'maximum amount of times to broadcast a transaction before giving up and re-creating it, default 4') parser.add_option( '--maxcreatetx', type='int', dest='maxcreatetx', default=9, help= 'maximum amount of times to re-create a transaction before giving up, default 9') (options, args) = parser.parse_args() options = vars(options) if len(args) < 1: parser.error('Needs a wallet file') sys.exit(0) wallet_file = args[0] destaddrs = args[1:] print(destaddrs) load_program_config() for addr in destaddrs: addr_valid, errormsg = validate_address(addr) if not addr_valid: print('ERROR: Address ' + addr + ' invalid. ' + errormsg) return # Dynamically estimate a realistic fee if it currently is the default value. # At this point we do not know even the number of our own inputs, so # we guess conservatively with 2 inputs and 2 outputs each if options['txfee'] == -1: options['txfee'] = max(options['txfee'], estimate_tx_fee(2, 2)) log.debug("Estimated miner/tx fee for each cj participant: "+str(options['txfee'])) assert(options['txfee'] >= 0) if len(destaddrs) > options['addrcount']: options['addrcount'] = len(destaddrs) if options['addrcount'] + 1 > options['mixdepthcount']: print('not enough mixing depths to pay to all destination addresses, ' 'increasing mixdepthcount') options['mixdepthcount'] = options['addrcount'] + 1 if options['donateamount'] > 10.0: # fat finger probably, or misunderstanding options['donateamount'] = 0.9 print(str(options)) tx_list = generate_tumbler_tx(destaddrs, options) if not tx_list: return tx_list2 = copy.deepcopy(tx_list) tx_dict = {} for tx in tx_list2: srcmixdepth = tx['srcmixdepth'] tx.pop('srcmixdepth') if srcmixdepth not in tx_dict: tx_dict[srcmixdepth] = [] tx_dict[srcmixdepth].append(tx) dbg_tx_list = [] for srcmixdepth, txlist in tx_dict.iteritems(): dbg_tx_list.append({'srcmixdepth': srcmixdepth, 'tx': txlist}) log.debug('tumbler transaction list') pprint(dbg_tx_list) total_wait = sum([tx['wait'] for tx in tx_list]) print('creates ' + str(len(tx_list)) + ' transactions in total') print('waits in total for ' + str(len(tx_list)) + ' blocks and ' + str( total_wait) + ' minutes') total_block_and_wait = len(tx_list) * 10 + total_wait print('estimated time taken ' + str(total_block_and_wait) + ' minutes or ' + str(round(total_block_and_wait / 60.0, 2)) + ' hours') if options['addrcount'] <= 1: print('=' * 50) print('WARNING: You are only using one destination address') print('this is very bad for privacy') print('=' * 50) ret = raw_input('tumble with these tx? (y/n):') if ret[0] != 'y': return # NOTE: possibly out of date documentation # a couple of modes # im-running-from-the-nsa, takes about 80 hours, costs a lot # python tumbler.py -a 10 -N 10 5 -c 10 5 -l 50 -M 10 wallet_file 1xxx # # quick and cheap, takes about 90 minutes # python tumbler.py -N 2 1 -c 3 0.001 -l 10 -M 3 -a 1 wallet_file 1xxx # # default, good enough for most, takes about 5 hours # python tumbler.py wallet_file 1xxx # # for quick testing # python tumbler.py -N 2 1 -c 3 0.001 -l 0.1 -M 3 -a 0 wallet_file 1xxx 1yyy wallet = Wallet(wallet_file, max_mix_depth=options['mixdepthsrc'] + options['mixdepthcount']) jm_single().bc_interface.sync_wallet(wallet) jm_single().wait_for_commitments = 1 mcs = [IRCMessageChannel(c) for c in get_irc_mchannels()] mcc = MessageChannelCollection(mcs) log.debug('starting tumbler') tumbler = Tumbler(mcc, wallet, tx_list, options) try: log.debug('connecting to message channels') mcc.run() except: log.debug('CRASHING, DUMPING EVERYTHING') debug_dump_object(wallet, ['addr_cache', 'keys', 'seed']) debug_dump_object(tumbler) debug_dump_object(tumbler.cjtx) import traceback log.debug(traceback.format_exc())
def main(): parser = OptionParser( usage='usage: %prog [options] [cjamount] [cjaddr] [changeaddr] [utxos..]', description=('Creates an unsigned coinjoin transaction. Outputs ' 'a partially signed transaction hex string. The user ' 'must sign their inputs independently and broadcast ' 'them. The JoinMarket protocol requires the taker to ' 'have a single p2pk UTXO input to use to ' 'authenticate the encrypted messages. For this ' 'reason you must pass auth utxo and the ' 'corresponding private key')) # for cjamount=0 do a sweep, and ignore change address parser.add_option('-f', '--txfee', action='store', type='int', dest='txfee', default=10000, help='total miner fee in satoshis, default=10000') parser.add_option( '-w', '--wait-time', action='store', type='float', dest='waittime', help='wait time in seconds to allow orders to arrive, default=5', default=5) parser.add_option('-N', '--makercount', action='store', type='int', dest='makercount', help='how many makers to coinjoin with, default=2', default=2) parser.add_option( '-C', '--choose-cheapest', action='store_true', dest='choosecheapest', default=False, help='override weightened offers picking and choose cheapest') parser.add_option( '-P', '--pick-orders', action='store_true', dest='pickorders', default=False, help= 'manually pick which orders to take. doesn\'t work while sweeping.') parser.add_option('--yes', action='store_true', dest='answeryes', default=False, help='answer yes to everything') # TODO implement parser.add_option('-n', '--no-network', # action='store_true', dest='nonetwork', default=False, help='dont query # the blockchain interface, instead user must supply value of UTXOs on ' # + ' command line in the format txid:output/value-in-satoshi') (options, args) = parser.parse_args() if len(args) < 4: parser.error( 'Needs an amount, destination address, change address and utxos ') sys.exit(0) cjamount = int(args[0]) destaddr = args[1] changeaddr = args[2] cold_utxos = args[3:] load_program_config() addr_valid1, errormsg1 = validate_address(destaddr) errormsg2 = None # if amount = 0 dont bother checking changeaddr so user can write any junk if cjamount != 0: addr_valid2, errormsg2 = validate_address(changeaddr) else: addr_valid2 = True if not addr_valid1 or not addr_valid2: if not addr_valid1: print 'ERROR: Address invalid. ' + errormsg1 else: print 'ERROR: Address invalid. ' + errormsg2 return query_result = jm_single().bc_interface.query_utxo_set(cold_utxos) if None in query_result: print query_result utxo_data = {} for utxo, data in zip(cold_utxos, query_result): utxo_data[utxo] = {'address': data['address'], 'value': data['value']} print("Got this utxo data: " + str(utxo_data)) if options.pickorders and cjamount != 0: # cant use for sweeping chooseOrdersFunc = pick_order elif options.choosecheapest: chooseOrdersFunc = cheapest_order_choose else: # choose randomly (weighted) chooseOrdersFunc = weighted_order_choose log.debug('starting sendpayment') wallet = AbstractWallet() wallet.unspent = None mcs = [IRCMessageChannel(c, jm_single().nickname) for c in get_irc_mchannels()] mcc = MessageChannelCollection(mcs) taker = CreateUnsignedTx(mcc, wallet, cjamount, destaddr, changeaddr, utxo_data, options, chooseOrdersFunc) try: log.debug('starting message channels') mcc.run() except: log.debug('CRASHING, DUMPING EVERYTHING') debug_dump_object(wallet, ['addr_cache', 'keys', 'wallet_name', 'seed']) debug_dump_object(taker) import traceback log.debug(traceback.format_exc())