def direct_send(wallet, amount, mixdepth, destaddr, answeryes=False): """Send coins directly from one mixdepth to one destination address; does not need IRC. Sweep as for normal sendpayment (set amount=0). """ #Sanity checks; note destaddr format is carefully checked in startup assert isinstance(mixdepth, int) assert mixdepth >= 0 assert isinstance(amount, int) assert amount >= 0 and amount < 10000000000 assert isinstance(wallet, Wallet) import bitcoin as btc from pprint import pformat if amount == 0: utxos = wallet.get_utxos_by_mixdepth()[mixdepth] if utxos == {}: log.error("There are no utxos in mixdepth: " + str(mixdepth) + ", quitting.") return total_inputs_val = sum([va['value'] for u, va in utxos.iteritems()]) fee_est = estimate_tx_fee(len(utxos), 1) outs = [{"address": destaddr, "value": total_inputs_val - fee_est}] else: initial_fee_est = estimate_tx_fee(8, 2) #8 inputs to be conservative utxos = wallet.select_utxos(mixdepth, amount + initial_fee_est) if len(utxos) < 8: fee_est = estimate_tx_fee(len(utxos), 2) else: fee_est = initial_fee_est total_inputs_val = sum([va['value'] for u, va in utxos.iteritems()]) changeval = total_inputs_val - fee_est - amount outs = [{"value": amount, "address": destaddr}] change_addr = wallet.get_internal_addr(mixdepth) outs.append({"value": changeval, "address": change_addr}) #Now ready to construct transaction log.info("Using a fee of : " + str(fee_est) + " satoshis.") if amount != 0: log.info("Using a change value of: " + str(changeval) + " satoshis.") tx = btc.mktx(utxos.keys(), outs) stx = btc.deserialize(tx) for index, ins in enumerate(stx['ins']): utxo = ins['outpoint']['hash'] + ':' + str(ins['outpoint']['index']) addr = utxos[utxo]['address'] tx = btc.sign(tx, index, wallet.get_key_from_addr(addr)) txsigned = btc.deserialize(tx) log.info("Got signed transaction:\n") log.info(tx + "\n") log.info(pformat(txsigned)) if not answeryes: if raw_input( 'Would you like to push to the network? (y/n):')[0] != 'y': log.info("You chose not to broadcast the transaction, quitting.") return jm_single().bc_interface.pushtx(tx) txid = btc.txhash(tx) log.info("Transaction sent: " + txid + ", shutting down")
def make_sign_and_push( ins_full, wallet, amount, output_addr=None, change_addr=None, hashcode=btc.SIGHASH_ALL, estimate_fee=False ): """Utility function for easily building transactions from wallets """ total = sum(x["value"] for x in ins_full.values()) ins = ins_full.keys() # random output address and change addr output_addr = wallet.get_new_addr(1, 1) if not output_addr else output_addr change_addr = wallet.get_new_addr(1, 0) if not change_addr else change_addr fee_est = estimate_tx_fee(len(ins), 2) if estimate_fee else 10000 outs = [{"value": amount, "address": output_addr}, {"value": total - amount - fee_est, "address": change_addr}] tx = btc.mktx(ins, outs) de_tx = btc.deserialize(tx) for index, ins in enumerate(de_tx["ins"]): utxo = ins["outpoint"]["hash"] + ":" + str(ins["outpoint"]["index"]) addr = ins_full[utxo]["address"] priv = wallet.get_key_from_addr(addr) if index % 2: priv = binascii.unhexlify(priv) tx = btc.sign(tx, index, priv, hashcode=hashcode) # pushtx returns False on any error print btc.deserialize(tx) push_succeed = jm_single().bc_interface.pushtx(tx) if push_succeed: return btc.txhash(tx) else: return False
def make_sign_and_push(ins_full, wallet, amount, output_addr=None, change_addr=None, hashcode=btc.SIGHASH_ALL, estimate_fee = False): total = sum(x['value'] for x in ins_full.values()) ins = ins_full.keys() #random output address and change addr output_addr = wallet.get_new_addr(1, 1) if not output_addr else output_addr change_addr = wallet.get_new_addr(1, 0) if not change_addr else change_addr fee_est = estimate_tx_fee(len(ins), 2) if estimate_fee else 10000 outs = [{'value': amount, 'address': output_addr}, {'value': total - amount - fee_est, 'address': change_addr}] tx = btc.mktx(ins, outs) de_tx = btc.deserialize(tx) for index, ins in enumerate(de_tx['ins']): utxo = ins['outpoint']['hash'] + ':' + str(ins['outpoint']['index']) addr = ins_full[utxo]['address'] priv = wallet.get_key_from_addr(addr) if index % 2: priv = binascii.unhexlify(priv) tx = btc.sign(tx, index, priv, hashcode=hashcode) #pushtx returns False on any error print btc.deserialize(tx) push_succeed = jm_single().bc_interface.pushtx(tx) if push_succeed: return btc.txhash(tx) else: return False
def create_tx(self): time.sleep(self.taker.waittime) crow = self.taker.db.execute( 'SELECT COUNT(DISTINCT counterparty) FROM orderbook;').fetchone() counterparty_count = crow['COUNT(DISTINCT counterparty)'] counterparty_count -= len(self.ignored_makers) if counterparty_count < self.taker.makercount: log.debug('not enough counterparties to fill order, ending') #NB: don't shutdown msgchan here, that is done by the caller #after setting GUI state to reflect the reason for shutdown. return None, None, None, None utxos = None orders = None cjamount = None change_addr = None choose_orders_recover = None if self.taker.amount == 0: utxos = self.taker.wallet.get_utxos_by_mixdepth()[ self.taker.mixdepth] #do our best to estimate the fee based on the number of #our own utxos; this estimate may be significantly higher #than the default set in option.txfee * makercount, where #we have a large number of utxos to spend. If it is smaller, #we'll be conservative and retain the original estimate. est_ins = len(utxos) + 3 * self.taker.makercount log.debug("Estimated ins: " + str(est_ins)) est_outs = 2 * self.taker.makercount + 1 log.debug("Estimated outs: " + str(est_outs)) estimated_fee = estimate_tx_fee(est_ins, est_outs) log.debug("We have a fee estimate: " + str(estimated_fee)) log.debug("And a requested fee of: " + str(self.taker.txfee * self.taker.makercount)) if estimated_fee > self.taker.makercount * self.taker.txfee: #both values are integers; we can ignore small rounding errors self.taker.txfee = estimated_fee / self.taker.makercount total_value = sum([va['value'] for va in utxos.values()]) orders, cjamount = choose_sweep_orders(self.taker.db, total_value, self.taker.txfee, self.taker.makercount, self.taker.chooseOrdersFunc, self.ignored_makers) if not orders: raise Exception( "Could not find orders to complete transaction.") total_cj_fee = total_value - cjamount - \ self.taker.txfee*self.taker.makercount else: orders, total_cj_fee = self.sendpayment_choose_orders( self.taker.amount, self.taker.makercount) cjamount = self.taker.amount if not orders: log.debug( 'ERROR not enough liquidity in the orderbook, exiting') return None, None, None, None return orders, total_cj_fee, cjamount, utxos
def create_tx(self): time.sleep(self.taker.waittime) crow = self.taker.db.execute( 'SELECT COUNT(DISTINCT counterparty) FROM orderbook;').fetchone() counterparty_count = crow['COUNT(DISTINCT counterparty)'] counterparty_count -= len(self.ignored_makers) if counterparty_count < self.taker.makercount: log.debug('not enough counterparties to fill order, ending') #NB: don't shutdown msgchan here, that is done by the caller #after setting GUI state to reflect the reason for shutdown. return None, None, None, None utxos = None orders = None cjamount = None change_addr = None choose_orders_recover = None if self.taker.amount == 0: utxos = self.taker.wallet.get_utxos_by_mixdepth()[ self.taker.mixdepth] #do our best to estimate the fee based on the number of #our own utxos; this estimate may be significantly higher #than the default set in option.txfee * makercount, where #we have a large number of utxos to spend. If it is smaller, #we'll be conservative and retain the original estimate. est_ins = len(utxos)+3*self.taker.makercount log.debug("Estimated ins: "+str(est_ins)) est_outs = 2*self.taker.makercount + 1 log.debug("Estimated outs: "+str(est_outs)) estimated_fee = estimate_tx_fee(est_ins, est_outs) log.debug("We have a fee estimate: "+str(estimated_fee)) log.debug("And a requested fee of: "+str( self.taker.txfee * self.taker.makercount)) if estimated_fee > self.taker.makercount * self.taker.txfee: #both values are integers; we can ignore small rounding errors self.taker.txfee = estimated_fee / self.taker.makercount total_value = sum([va['value'] for va in utxos.values()]) orders, cjamount = choose_sweep_orders( self.taker.db, total_value, self.taker.txfee, self.taker.makercount, self.taker.chooseOrdersFunc, self.ignored_makers) if not orders: raise Exception("Could not find orders to complete transaction.") total_cj_fee = total_value - cjamount - \ self.taker.txfee*self.taker.makercount else: orders, total_cj_fee = self.sendpayment_choose_orders( self.taker.amount, self.taker.makercount) cjamount = self.taker.amount if not orders: log.debug( 'ERROR not enough liquidity in the orderbook, exiting') return None, None, None, None return orders, total_cj_fee, cjamount, utxos
def make_sign_and_push(ins_full, wallet, amount, output_addr=None, change_addr=None, hashcode=btc.SIGHASH_ALL, estimate_fee=False): """Utility function for easily building transactions from wallets """ total = sum(x['value'] for x in ins_full.values()) ins = ins_full.keys() #random output address and change addr output_addr = wallet.get_new_addr(1, 1) if not output_addr else output_addr change_addr = wallet.get_new_addr(1, 0) if not change_addr else change_addr fee_est = estimate_tx_fee(len(ins), 2) if estimate_fee else 10000 outs = [{ 'value': amount, 'address': output_addr }, { 'value': total - amount - fee_est, 'address': change_addr }] tx = btc.mktx(ins, outs) de_tx = btc.deserialize(tx) for index, ins in enumerate(de_tx['ins']): utxo = ins['outpoint']['hash'] + ':' + str(ins['outpoint']['index']) addr = ins_full[utxo]['address'] priv = wallet.get_key_from_addr(addr) if index % 2: priv = binascii.unhexlify(priv) tx = btc.sign(tx, index, priv, hashcode=hashcode) #pushtx returns False on any error print btc.deserialize(tx) push_succeed = jm_single().bc_interface.pushtx(tx) if push_succeed: return btc.txhash(tx) else: return False
def sign(utxo, priv, destaddrs): """Sign a tx sending the amount amt, from utxo utxo, equally to each of addresses in list destaddrs, after fees; the purpose is to create a large number of utxos. """ results = validate_utxo_data([(utxo, priv)], retrieve=True) if not results: return False assert results[0][0] == utxo amt = results[0][1] ins = [utxo] estfee = estimate_tx_fee(1, len(destaddrs)) outs = [] share = int((amt - estfee) / len(destaddrs)) fee = amt - share*len(destaddrs) assert fee >= estfee log.debug("Using fee: " + str(fee)) for i, addr in enumerate(destaddrs): outs.append({'address': addr, 'value': share}) unsigned_tx = btc.mktx(ins, outs) return btc.sign(unsigned_tx, 0, btc.from_wif_privkey( priv, vbyte=get_p2pk_vbyte()))
def sign(utxo, priv, destaddrs): """Sign a tx sending the amount amt, from utxo utxo, equally to each of addresses in list destaddrs, after fees; the purpose is to create a large number of utxos. """ results = validate_utxo_data([(utxo, priv)], retrieve=True) if not results: return False assert results[0][0] == utxo amt = results[0][1] ins = [utxo] estfee = estimate_tx_fee(1, len(destaddrs)) outs = [] share = int((amt - estfee) / len(destaddrs)) fee = amt - share * len(destaddrs) assert fee >= estfee log.info("Using fee: " + str(fee)) for i, addr in enumerate(destaddrs): outs.append({'address': addr, 'value': share}) unsigned_tx = btc.mktx(ins, outs) return btc.sign(unsigned_tx, 0, btc.from_wif_privkey(priv, vbyte=get_p2pk_vbyte()))
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 create_tx(self): if self.create_tx_attempts == 0: log.debug('reached limit of number of attempts to create tx, quitting') self.taker.msgchan.shutdown() return jm_single().bc_interface.sync_unspent(self.taker.wallet) self.create_tx_attempts -= 1 orders = None cj_amount = 0 change_addr = None choose_orders_recover = None if self.sweep: log.debug('sweeping') utxos = self.taker.wallet.get_utxos_by_mixdepth()[self.tx[ 'srcmixdepth']] #do our best to estimate the fee based on the number of #our own utxos; this estimate may be significantly higher #than the default set in option.txfee * makercount, where #we have a large number of utxos to spend. If it is smaller, #we'll be conservative and retain the original estimate. est_ins = len(utxos)+3*self.tx['makercount'] log.debug("Estimated ins: "+str(est_ins)) est_outs = 2*self.tx['makercount'] + 1 log.debug("Estimated outs: "+str(est_outs)) estimated_fee = estimate_tx_fee(est_ins, est_outs) log.debug("We have a fee estimate: "+str(estimated_fee)) log.debug("And a requested fee of: "+str( self.taker.options['txfee'] * self.tx['makercount'])) fee_for_tx = max([estimated_fee, self.tx['makercount'] * self.taker.options['txfee']]) fee_for_tx = int(fee_for_tx / self.tx['makercount']) total_value = sum([addrval['value'] for addrval in utxos.values()]) while True: orders, cj_amount, total_cj_fee = choose_sweep_orders( self.taker.db, total_value, fee_for_tx, self.tx['makercount'], weighted_order_choose, self.ignored_makers) if orders is None: log.debug('waiting for liquidity ' + str( self.taker.options['liquiditywait']) + 'secs, hopefully more orders should come in') time.sleep(self.taker.options['liquiditywait']) continue abs_cj_fee = 1.0 * total_cj_fee / self.tx['makercount'] rel_cj_fee = abs_cj_fee / cj_amount log.debug( 'rel/abs average fee = ' + str(rel_cj_fee) + ' / ' + str( abs_cj_fee)) if rel_cj_fee > self.taker.options['maxcjfee'][0] \ and abs_cj_fee > self.taker.options['maxcjfee'][1]: log.debug('cj fee higher than maxcjfee, waiting ' + str( self.taker.options['liquiditywait']) + ' seconds') time.sleep(self.taker.options['liquiditywait']) continue break else: if self.tx['amount_fraction'] == 0: cj_amount = int(self.balance * self.taker.options['donateamount'] / 100.0) self.destaddr = None else: cj_amount = int(self.tx['amount_fraction'] * self.balance) if cj_amount < self.taker.options['mincjamount']: log.debug('cj amount too low, bringing up') cj_amount = self.taker.options['mincjamount'] change_addr = self.taker.wallet.get_internal_addr( self.tx['srcmixdepth']) log.debug('coinjoining ' + str(cj_amount) + ' satoshi') orders, total_cj_fee = self.tumbler_choose_orders( cj_amount, self.tx['makercount']) total_amount = cj_amount + total_cj_fee + \ self.taker.options['txfee']*self.tx['makercount'] log.debug('total estimated amount spent = ' + str(total_amount)) #adjust the required amount upwards to anticipate an increase of the #transaction fee after re-estimation; this is sufficiently conservative #to make failures unlikely while keeping the occurence of failure to #find sufficient utxos extremely rare. Indeed, a doubling of 'normal' #txfee indicates undesirable behaviour on maker side anyway. try: utxos = self.taker.wallet.select_utxos(self.tx['srcmixdepth'], total_amount+self.taker.options['txfee']*self.tx['makercount']) except Exception as e: #we cannot afford to just throw not enough funds; better to #try with a smaller request; it could still fail within #CoinJoinTX.recv_txio, but make every effort to avoid stopping. if str(e) == "Not enough funds": log.debug("Failed to select total amount + twice txfee from" + "wallet; trying to select just total amount.") utxos = self.taker.wallet.select_utxos(self.tx['srcmixdepth'], total_amount) else: raise fee_for_tx = self.taker.options['txfee'] choose_orders_recover = self.tumbler_choose_orders self.taker.start_cj(self.taker.wallet, cj_amount, orders, utxos, self.destaddr, change_addr, fee_for_tx*self.tx['makercount'], self.finishcallback, choose_orders_recover)
def create_tx(self): crow = self.taker.db.execute( 'SELECT COUNT(DISTINCT counterparty) FROM orderbook;').fetchone() counterparty_count = crow['COUNT(DISTINCT counterparty)'] counterparty_count -= len(self.ignored_makers) if counterparty_count < self.taker.makercount: print('not enough counterparties to fill order, ending') self.taker.msgchan.shutdown() return utxos = None orders = None cjamount = 0 change_addr = None choose_orders_recover = None if self.taker.amount == 0: utxos = self.taker.wallet.get_utxos_by_mixdepth()[ self.taker.mixdepth] #do our best to estimate the fee based on the number of #our own utxos; this estimate may be significantly higher #than the default set in option.txfee * makercount, where #we have a large number of utxos to spend. If it is smaller, #we'll be conservative and retain the original estimate. est_ins = len(utxos) + 3 * self.taker.makercount log.debug("Estimated ins: " + str(est_ins)) est_outs = 2 * self.taker.makercount + 1 log.debug("Estimated outs: " + str(est_outs)) estimated_fee = estimate_tx_fee(est_ins, est_outs) log.debug("We have a fee estimate: " + str(estimated_fee)) log.debug("And a requested fee of: " + str(self.taker.txfee * self.taker.makercount)) if estimated_fee > self.taker.makercount * self.taker.txfee: #both values are integers; we can ignore small rounding errors self.taker.txfee = estimated_fee / self.taker.makercount total_value = sum([va['value'] for va in utxos.values()]) orders, cjamount = choose_sweep_orders(self.taker.db, total_value, self.taker.txfee, self.taker.makercount, self.taker.chooseOrdersFunc, self.ignored_makers) if not orders: raise Exception( "Could not find orders to complete transaction.") if not self.taker.answeryes: total_cj_fee = total_value - cjamount - \ self.taker.txfee*self.taker.makercount log.debug('total cj fee = ' + str(total_cj_fee)) total_fee_pc = 1.0 * total_cj_fee / cjamount log.debug('total coinjoin fee = ' + str(float('%.3g' % (100.0 * total_fee_pc))) + '%') check_high_fee(total_fee_pc) if raw_input('send with these orders? (y/n):')[0] != 'y': self.taker.msgchan.shutdown() return else: orders, total_cj_fee = self.sendpayment_choose_orders( self.taker.amount, self.taker.makercount) if not orders: log.debug( 'ERROR not enough liquidity in the orderbook, exiting') return total_amount = self.taker.amount + total_cj_fee + \ self.taker.txfee*self.taker.makercount print 'total estimated amount spent = ' + str(total_amount) #adjust the required amount upwards to anticipate a tripling of #transaction fee after re-estimation; this is sufficiently conservative #to make failures unlikely while keeping the occurence of failure to #find sufficient utxos extremely rare. Indeed, a tripling of 'normal' #txfee indicates undesirable behaviour on maker side anyway. utxos = self.taker.wallet.select_utxos( self.taker.mixdepth, total_amount + 2 * self.taker.txfee * self.taker.makercount) cjamount = self.taker.amount change_addr = self.taker.wallet.get_internal_addr( self.taker.mixdepth) choose_orders_recover = self.sendpayment_choose_orders self.taker.start_cj(self.taker.wallet, cjamount, orders, utxos, self.taker.destaddr, change_addr, self.taker.makercount * self.taker.txfee, self.finishcallback, choose_orders_recover)
def recv_txio(self, nick, utxo_list, cj_pub, change_addr): if nick not in self.nonrespondants: log.debug(('recv_txio => nick={} not in ' 'nonrespondants {}').format(nick, self.nonrespondants)) return self.utxos[nick] = utxo_list order = self.db.execute( 'SELECT ordertype, txfee, cjfee FROM ' 'orderbook WHERE oid=? AND counterparty=?', (self.active_orders[nick], nick)).fetchone() utxo_data = jm_single().bc_interface.query_utxo_set(self.utxos[nick]) if None in utxo_data: log.debug(('ERROR outputs unconfirmed or already spent. ' 'utxo_data={}').format(pprint.pformat(utxo_data))) # when internal reviewing of makers is created, add it here to # immediately quit return # ignore this message, eventually the timeout thread will recover total_input = sum([d['value'] for d in utxo_data]) real_cjfee = calc_cj_fee(order['ordertype'], order['cjfee'], self.cj_amount) self.outputs.append({ 'address': change_addr, 'value': total_input - self.cj_amount - order['txfee'] + real_cjfee }) fmt = ('fee breakdown for {} totalin={:d} ' 'cjamount={:d} txfee={:d} realcjfee={:d}').format log.debug( fmt(nick, total_input, self.cj_amount, order['txfee'], real_cjfee)) cj_addr = btc.pubtoaddr(cj_pub, get_p2pk_vbyte()) self.outputs.append({'address': cj_addr, 'value': self.cj_amount}) self.cjfee_total += real_cjfee self.maker_txfee_contributions += order['txfee'] self.nonrespondants.remove(nick) if len(self.nonrespondants) > 0: log.debug('nonrespondants = ' + str(self.nonrespondants)) return self.all_responded = True with self.timeout_lock: self.timeout_lock.notify() log.debug('got all parts, enough to build a tx') self.nonrespondants = list(self.active_orders.keys()) my_total_in = sum( [va['value'] for u, va in self.input_utxos.iteritems()]) if self.my_change_addr: #Estimate fee per choice of next/3/6 blocks targetting. estimated_fee = estimate_tx_fee(len(sum(self.utxos.values(), [])), len(self.outputs) + 2) log.debug("Based on initial guess: " + str(self.total_txfee) + ", we estimated a fee of: " + str(estimated_fee)) #reset total self.total_txfee = estimated_fee my_txfee = max(self.total_txfee - self.maker_txfee_contributions, 0) my_change_value = (my_total_in - self.cj_amount - self.cjfee_total - my_txfee) #Since we could not predict the maker's inputs, we may end up needing #too much such that the change value is negative or small. Note that #we have tried to avoid this based on over-estimating the needed amount #in SendPayment.create_tx(), but it is still a possibility if one maker #uses a *lot* of inputs. if self.my_change_addr and my_change_value <= 0: raise ValueError("Calculated transaction fee of: " + str(self.total_txfee) + " is too large for our inputs;Please try again.") elif self.my_change_addr and my_change_value <= jm_single( ).DUST_THRESHOLD: log.debug("Dynamically calculated change lower than dust: " + str(my_change_value) + "; dropping.") self.my_change_addr = None my_change_value = 0 log.debug( 'fee breakdown for me totalin=%d my_txfee=%d makers_txfee=%d cjfee_total=%d => changevalue=%d' % (my_total_in, my_txfee, self.maker_txfee_contributions, self.cjfee_total, my_change_value)) if self.my_change_addr is None: if my_change_value != 0 and abs(my_change_value) != 1: # seems you wont always get exactly zero because of integer # rounding so 1 satoshi extra or fewer being spent as miner # fees is acceptable log.debug(('WARNING CHANGE NOT BEING ' 'USED\nCHANGEVALUE = {}').format(my_change_value)) else: self.outputs.append({ 'address': self.my_change_addr, 'value': my_change_value }) self.utxo_tx = [ dict([('output', u)]) for u in sum(self.utxos.values(), []) ] self.outputs.append({ 'address': self.coinjoin_address(), 'value': self.cj_amount }) random.shuffle(self.utxo_tx) random.shuffle(self.outputs) tx = btc.mktx(self.utxo_tx, self.outputs) log.debug('obtained tx\n' + pprint.pformat(btc.deserialize(tx))) self.msgchan.send_tx(self.active_orders.keys(), tx) self.latest_tx = btc.deserialize(tx) for index, ins in enumerate(self.latest_tx['ins']): utxo = ins['outpoint']['hash'] + ':' + str( ins['outpoint']['index']) if utxo not in self.input_utxos.keys(): continue # placeholders required ins['script'] = 'deadbeef'
def create_tx(self): crow = self.taker.db.execute( 'SELECT COUNT(DISTINCT counterparty) FROM orderbook;').fetchone() counterparty_count = crow['COUNT(DISTINCT counterparty)'] counterparty_count -= len(self.ignored_makers) if counterparty_count < self.taker.makercount: print('not enough counterparties to fill order, ending') self.taker.msgchan.shutdown() return utxos = None orders = None cjamount = 0 change_addr = None choose_orders_recover = None if self.taker.amount == 0: utxos = self.taker.wallet.get_utxos_by_mixdepth()[ self.taker.mixdepth] #do our best to estimate the fee based on the number of #our own utxos; this estimate may be significantly higher #than the default set in option.txfee * makercount, where #we have a large number of utxos to spend. If it is smaller, #we'll be conservative and retain the original estimate. est_ins = len(utxos)+3*self.taker.makercount log.debug("Estimated ins: "+str(est_ins)) est_outs = 2*self.taker.makercount + 1 log.debug("Estimated outs: "+str(est_outs)) estimated_fee = estimate_tx_fee(est_ins, est_outs) log.debug("We have a fee estimate: "+str(estimated_fee)) log.debug("And a requested fee of: "+str( self.taker.txfee * self.taker.makercount)) if estimated_fee > self.taker.makercount * self.taker.txfee: #both values are integers; we can ignore small rounding errors self.taker.txfee = estimated_fee / self.taker.makercount total_value = sum([va['value'] for va in utxos.values()]) orders, cjamount = choose_sweep_orders( self.taker.db, total_value, self.taker.txfee, self.taker.makercount, self.taker.chooseOrdersFunc, self.ignored_makers) if not orders: raise Exception("Could not find orders to complete transaction.") if not self.taker.answeryes: total_cj_fee = total_value - cjamount - \ self.taker.txfee*self.taker.makercount log.debug('total cj fee = ' + str(total_cj_fee)) total_fee_pc = 1.0 * total_cj_fee / cjamount log.debug('total coinjoin fee = ' + str(float('%.3g' % ( 100.0 * total_fee_pc))) + '%') check_high_fee(total_fee_pc) if raw_input('send with these orders? (y/n):')[0] != 'y': self.taker.msgchan.shutdown() return else: orders, total_cj_fee = self.sendpayment_choose_orders( self.taker.amount, self.taker.makercount) if not orders: log.debug( 'ERROR not enough liquidity in the orderbook, exiting') return total_amount = self.taker.amount + total_cj_fee + \ self.taker.txfee*self.taker.makercount print 'total estimated amount spent = ' + str(total_amount) #adjust the required amount upwards to anticipate a tripling of #transaction fee after re-estimation; this is sufficiently conservative #to make failures unlikely while keeping the occurence of failure to #find sufficient utxos extremely rare. Indeed, a tripling of 'normal' #txfee indicates undesirable behaviour on maker side anyway. utxos = self.taker.wallet.select_utxos(self.taker.mixdepth, total_amount+2*self.taker.txfee*self.taker.makercount) cjamount = self.taker.amount change_addr = self.taker.wallet.get_internal_addr(self.taker.mixdepth) choose_orders_recover = self.sendpayment_choose_orders self.taker.start_cj(self.taker.wallet, cjamount, orders, utxos, self.taker.destaddr, change_addr, self.taker.makercount*self.taker.txfee, self.finishcallback, choose_orders_recover)
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 recv_txio(self, nick, utxo_list, cj_pub, change_addr): if nick not in self.nonrespondants: log.debug(('recv_txio => nick={} not in ' 'nonrespondants {}').format(nick, self.nonrespondants)) return self.utxos[nick] = utxo_list utxo_data = jm_single().bc_interface.query_utxo_set(self.utxos[nick]) if None in utxo_data: log.debug(('ERROR outputs unconfirmed or already spent. ' 'utxo_data={}').format(pprint.pformat(utxo_data))) # when internal reviewing of makers is created, add it here to # immediately quit; currently, the timeout thread suffices. return total_input = sum([d['value'] for d in utxo_data]) real_cjfee = calc_cj_fee(self.active_orders[nick]['ordertype'], self.active_orders[nick]['cjfee'], self.cj_amount) change_amount = (total_input - self.cj_amount - self.active_orders[nick]['txfee'] + real_cjfee) # certain malicious and/or incompetent liquidity providers send # inputs totalling less than the coinjoin amount! this leads to # a change output of zero satoshis, so the invalid transaction # fails harmlessly; let's fail earlier, with a clear message. if change_amount < jm_single().DUST_THRESHOLD: fmt = ('ERROR counterparty requires sub-dust change. nick={}' 'totalin={:d} cjamount={:d} change={:d}').format log.debug(fmt(nick, total_input, self.cj_amount, change_amount)) return # timeout marks this maker as nonresponsive self.outputs.append({'address': change_addr, 'value': change_amount}) fmt = ('fee breakdown for {} totalin={:d} ' 'cjamount={:d} txfee={:d} realcjfee={:d}').format log.debug(fmt(nick, total_input, self.cj_amount, self.active_orders[nick]['txfee'], real_cjfee)) cj_addr = btc.pubtoaddr(cj_pub, get_p2pk_vbyte()) self.outputs.append({'address': cj_addr, 'value': self.cj_amount}) self.cjfee_total += real_cjfee self.maker_txfee_contributions += self.active_orders[nick]['txfee'] self.nonrespondants.remove(nick) if len(self.nonrespondants) > 0: log.debug('nonrespondants = ' + str(self.nonrespondants)) return log.debug('got all parts, enough to build a tx') self.nonrespondants = list(self.active_orders.keys()) my_total_in = sum([va['value'] for u, va in self.input_utxos.iteritems()]) if self.my_change_addr: #Estimate fee per choice of next/3/6 blocks targetting. estimated_fee = estimate_tx_fee(len(sum( self.utxos.values(),[])), len(self.outputs)+2) log.debug("Based on initial guess: "+str( self.total_txfee)+", we estimated a fee of: "+str(estimated_fee)) #reset total self.total_txfee = estimated_fee my_txfee = max(self.total_txfee - self.maker_txfee_contributions, 0) my_change_value = ( my_total_in - self.cj_amount - self.cjfee_total - my_txfee) #Since we could not predict the maker's inputs, we may end up needing #too much such that the change value is negative or small. Note that #we have tried to avoid this based on over-estimating the needed amount #in SendPayment.create_tx(), but it is still a possibility if one maker #uses a *lot* of inputs. if self.my_change_addr and my_change_value <= 0: raise ValueError("Calculated transaction fee of: "+str( self.total_txfee)+" is too large for our inputs;Please try again.") elif self.my_change_addr and my_change_value <= jm_single().DUST_THRESHOLD: log.debug("Dynamically calculated change lower than dust: "+str( my_change_value)+"; dropping.") self.my_change_addr = None my_change_value = 0 log.debug('fee breakdown for me totalin=%d my_txfee=%d makers_txfee=%d cjfee_total=%d => changevalue=%d' % (my_total_in, my_txfee, self.maker_txfee_contributions, self.cjfee_total, my_change_value)) if self.my_change_addr is None: if my_change_value != 0 and abs(my_change_value) != 1: # seems you wont always get exactly zero because of integer # rounding so 1 satoshi extra or fewer being spent as miner # fees is acceptable log.debug(('WARNING CHANGE NOT BEING ' 'USED\nCHANGEVALUE = {}').format(my_change_value)) else: self.outputs.append({'address': self.my_change_addr, 'value': my_change_value}) self.utxo_tx = [dict([('output', u)]) for u in sum(self.utxos.values(), [])] self.outputs.append({'address': self.coinjoin_address(), 'value': self.cj_amount}) random.shuffle(self.utxo_tx) random.shuffle(self.outputs) tx = btc.mktx(self.utxo_tx, self.outputs) log.debug('obtained tx\n' + pprint.pformat(btc.deserialize(tx))) #Re-calculate a sensible timeout wait based on the throttling #settings and the tx size. #Calculation: Let tx size be S; tx undergoes two b64 expansions, 1.8*S #So we're sending N*1.8*S over the wire, and the #maximum bytes/sec = B, means we need (1.8*N*S/B) seconds, #and need to add some leeway for network delays, we just add the #contents of jm_single().maker_timeout_sec (the user configured value) self.maker_timeout_sec = (len(tx) * 1.8 * len( self.active_orders.keys()))/(B_PER_SEC) + jm_single().maker_timeout_sec log.debug("Based on transaction size: " + str( len(tx)) + ", calculated time to wait for replies: " + str( self.maker_timeout_sec)) self.all_responded = True with self.timeout_lock: self.timeout_lock.notify() self.msgchan.send_tx(self.active_orders.keys(), tx) self.latest_tx = btc.deserialize(tx) for index, ins in enumerate(self.latest_tx['ins']): utxo = ins['outpoint']['hash'] + ':' + str( ins['outpoint']['index']) if utxo not in self.input_utxos.keys(): continue # placeholders required ins['script'] = 'deadbeef'
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] [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 recv_txio(self, nick, utxo_list, auth_pub, cj_addr, change_addr): if nick not in self.nonrespondants: log.debug(('recv_txio => nick={} not in ' 'nonrespondants {}').format(nick, self.nonrespondants)) return self.utxos[nick] = utxo_list utxo_data = jm_single().bc_interface.query_utxo_set(self.utxos[nick]) if None in utxo_data: log.debug(('ERROR outputs unconfirmed or already spent. ' 'utxo_data={}').format(pprint.pformat(utxo_data))) # when internal reviewing of makers is created, add it here to # immediately quit; currently, the timeout thread suffices. return #Complete maker authorization: #Extract the address fields from the utxos #Construct the Bitcoin address for the auth_pub field #Ensure that at least one address from utxos corresponds. input_addresses = [d['address'] for d in utxo_data] auth_address = btc.pubkey_to_address(auth_pub, get_p2pk_vbyte()) if not auth_address in input_addresses: log.debug("ERROR maker's authorising pubkey is not included " "in the transaction: " + str(auth_address)) return total_input = sum([d['value'] for d in utxo_data]) real_cjfee = calc_cj_fee(self.active_orders[nick]['ordertype'], self.active_orders[nick]['cjfee'], self.cj_amount) change_amount = (total_input - self.cj_amount - self.active_orders[nick]['txfee'] + real_cjfee) # certain malicious and/or incompetent liquidity providers send # inputs totalling less than the coinjoin amount! this leads to # a change output of zero satoshis, so the invalid transaction # fails harmlessly; let's fail earlier, with a clear message. if change_amount < jm_single().DUST_THRESHOLD: fmt = ('ERROR counterparty requires sub-dust change. nick={}' 'totalin={:d} cjamount={:d} change={:d}').format log.debug(fmt(nick, total_input, self.cj_amount, change_amount)) return # timeout marks this maker as nonresponsive self.outputs.append({'address': change_addr, 'value': change_amount}) fmt = ('fee breakdown for {} totalin={:d} ' 'cjamount={:d} txfee={:d} realcjfee={:d}').format log.debug( fmt(nick, total_input, self.cj_amount, self.active_orders[nick]['txfee'], real_cjfee)) self.outputs.append({'address': cj_addr, 'value': self.cj_amount}) self.cjfee_total += real_cjfee self.maker_txfee_contributions += self.active_orders[nick]['txfee'] self.nonrespondants.remove(nick) if len(self.nonrespondants) > 0: log.debug('nonrespondants = ' + str(self.nonrespondants)) return log.debug('got all parts, enough to build a tx') self.nonrespondants = list(self.active_orders.keys()) my_total_in = sum( [va['value'] for u, va in self.input_utxos.iteritems()]) if self.my_change_addr: #Estimate fee per choice of next/3/6 blocks targetting. estimated_fee = estimate_tx_fee(len(sum(self.utxos.values(), [])), len(self.outputs) + 2) log.debug("Based on initial guess: " + str(self.total_txfee) + ", we estimated a fee of: " + str(estimated_fee)) #reset total self.total_txfee = estimated_fee my_txfee = max(self.total_txfee - self.maker_txfee_contributions, 0) my_change_value = (my_total_in - self.cj_amount - self.cjfee_total - my_txfee) #Since we could not predict the maker's inputs, we may end up needing #too much such that the change value is negative or small. Note that #we have tried to avoid this based on over-estimating the needed amount #in SendPayment.create_tx(), but it is still a possibility if one maker #uses a *lot* of inputs. if self.my_change_addr and my_change_value <= 0: raise ValueError("Calculated transaction fee of: " + str(self.total_txfee) + " is too large for our inputs;Please try again.") elif self.my_change_addr and my_change_value <= jm_single( ).DUST_THRESHOLD: log.debug("Dynamically calculated change lower than dust: " + str(my_change_value) + "; dropping.") self.my_change_addr = None my_change_value = 0 log.debug( 'fee breakdown for me totalin=%d my_txfee=%d makers_txfee=%d cjfee_total=%d => changevalue=%d' % (my_total_in, my_txfee, self.maker_txfee_contributions, self.cjfee_total, my_change_value)) if self.my_change_addr is None: if my_change_value != 0 and abs(my_change_value) != 1: # seems you wont always get exactly zero because of integer # rounding so 1 satoshi extra or fewer being spent as miner # fees is acceptable log.debug(('WARNING CHANGE NOT BEING ' 'USED\nCHANGEVALUE = {}').format(my_change_value)) else: self.outputs.append({ 'address': self.my_change_addr, 'value': my_change_value }) self.utxo_tx = [ dict([('output', u)]) for u in sum(self.utxos.values(), []) ] self.outputs.append({ 'address': self.coinjoin_address(), 'value': self.cj_amount }) random.shuffle(self.utxo_tx) random.shuffle(self.outputs) tx = btc.mktx(self.utxo_tx, self.outputs) log.debug('obtained tx\n' + pprint.pformat(btc.deserialize(tx))) #Re-calculate a sensible timeout wait based on the throttling #settings and the tx size. #Calculation: Let tx size be S; tx undergoes two b64 expansions, 1.8*S #So we're sending N*1.8*S over the wire, and the #maximum bytes/sec = B, means we need (1.8*N*S/B) seconds, #and need to add some leeway for network delays, we just add the #contents of jm_single().maker_timeout_sec (the user configured value) self.maker_timeout_sec = (len(tx) * 1.8 * len(self.active_orders.keys( ))) / (B_PER_SEC) + jm_single().maker_timeout_sec log.debug("Based on transaction size: " + str(len(tx)) + ", calculated time to wait for replies: " + str(self.maker_timeout_sec)) self.all_responded = True with self.timeout_lock: self.timeout_lock.notify() self.msgchan.send_tx(self.active_orders.keys(), tx) self.latest_tx = btc.deserialize(tx) for index, ins in enumerate(self.latest_tx['ins']): utxo = ins['outpoint']['hash'] + ':' + str( ins['outpoint']['index']) if utxo not in self.input_utxos.keys(): continue # placeholders required ins['script'] = 'deadbeef'
def create_tx(self): if self.create_tx_attempts == 0: log.debug( 'reached limit of number of attempts to create tx, quitting') self.taker.msgchan.shutdown() return jm_single().bc_interface.sync_unspent(self.taker.wallet) self.create_tx_attempts -= 1 orders = None cj_amount = 0 change_addr = None choose_orders_recover = None if self.sweep: log.debug('sweeping') utxos = self.taker.wallet.get_utxos_by_mixdepth()[ self.tx['srcmixdepth']] #do our best to estimate the fee based on the number of #our own utxos; this estimate may be significantly higher #than the default set in option.txfee * makercount, where #we have a large number of utxos to spend. If it is smaller, #we'll be conservative and retain the original estimate. est_ins = len(utxos) + 3 * self.tx['makercount'] log.debug("Estimated ins: " + str(est_ins)) est_outs = 2 * self.tx['makercount'] + 1 log.debug("Estimated outs: " + str(est_outs)) estimated_fee = estimate_tx_fee(est_ins, est_outs) log.debug("We have a fee estimate: " + str(estimated_fee)) log.debug("And a requested fee of: " + str(self.taker.options.txfee * self.tx['makercount'])) fee_for_tx = max([ estimated_fee, self.tx['makercount'] * self.taker.options.txfee ]) fee_for_tx = int(fee_for_tx / self.tx['makercount']) total_value = sum([addrval['value'] for addrval in utxos.values()]) while True: orders, cj_amount, total_cj_fee = choose_sweep_orders( self.taker.db, total_value, fee_for_tx, self.tx['makercount'], weighted_order_choose, self.ignored_makers) if orders is None: log.debug('waiting for liquidity ' + str(self.taker.options.liquiditywait) + 'secs, hopefully more orders should come in') time.sleep(self.taker.options.liquiditywait) continue abs_cj_fee = 1.0 * total_cj_fee / self.tx['makercount'] rel_cj_fee = abs_cj_fee / cj_amount log.debug('rel/abs average fee = ' + str(rel_cj_fee) + ' / ' + str(abs_cj_fee)) if rel_cj_fee > self.taker.options.maxcjfee[0] \ and abs_cj_fee > self.taker.options.maxcjfee[1]: log.debug('cj fee higher than maxcjfee, waiting ' + str(self.taker.options.liquiditywait) + ' seconds') time.sleep(self.taker.options.liquiditywait) continue break else: if self.tx['amount_fraction'] == 0: cj_amount = int(self.balance * self.taker.options.donateamount / 100.0) self.destaddr = None else: cj_amount = int(self.tx['amount_fraction'] * self.balance) if cj_amount < self.taker.options.mincjamount: log.debug('cj amount too low, bringing up') cj_amount = self.taker.options.mincjamount change_addr = self.taker.wallet.get_internal_addr( self.tx['srcmixdepth']) log.debug('coinjoining ' + str(cj_amount) + ' satoshi') orders, total_cj_fee = self.tumbler_choose_orders( cj_amount, self.tx['makercount']) total_amount = cj_amount + total_cj_fee + \ self.taker.options.txfee*self.tx['makercount'] log.debug('total estimated amount spent = ' + str(total_amount)) #adjust the required amount upwards to anticipate a tripling of #transaction fee after re-estimation; this is sufficiently conservative #to make failures unlikely while keeping the occurence of failure to #find sufficient utxos extremely rare. Indeed, a tripling of 'normal' #txfee indicates undesirable behaviour on maker side anyway. try: utxos = self.taker.wallet.select_utxos( self.tx['srcmixdepth'], total_amount + 2 * self.taker.options.txfee * self.tx['makercount']) except Exception as e: #we cannot afford to just throw not enough funds; better to #try with a smaller request; it could still fail within #CoinJoinTX.recv_txio, but make every effort to avoid stopping. if str(e) == "Not enough funds": log.debug( "Failed to select total amount + twice txfee from" + "wallet; trying to select just total amount.") utxos = self.taker.wallet.select_utxos( self.tx['srcmixdepth'], total_amount) else: raise fee_for_tx = self.taker.options.txfee choose_orders_recover = self.tumbler_choose_orders self.taker.start_cj(self.taker.wallet, cj_amount, orders, utxos, self.destaddr, change_addr, fee_for_tx * self.tx['makercount'], self.finishcallback, choose_orders_recover)
def recv_txio(self, nick, utxo_list, cj_pub, change_addr): if nick not in self.nonrespondants: log.debug(('recv_txio => nick={} not in ' 'nonrespondants {}').format(nick, self.nonrespondants)) return self.utxos[nick] = utxo_list utxo_data = jm_single().bc_interface.query_utxo_set(self.utxos[nick]) if None in utxo_data: log.debug(('ERROR outputs unconfirmed or already spent. ' 'utxo_data={}').format(pprint.pformat(utxo_data))) # when internal reviewing of makers is created, add it here to # immediately quit return # ignore this message, eventually the timeout thread will recover total_input = sum([d['value'] for d in utxo_data]) real_cjfee = calc_cj_fee(self.active_orders[nick]['ordertype'], self.active_orders[nick]['cjfee'], self.cj_amount) self.outputs.append({'address': change_addr, 'value': total_input - self.cj_amount - self.active_orders[nick]['txfee'] + real_cjfee}) fmt = ('fee breakdown for {} totalin={:d} ' 'cjamount={:d} txfee={:d} realcjfee={:d}').format log.debug(fmt(nick, total_input, self.cj_amount, self.active_orders[nick]['txfee'], real_cjfee)) cj_addr = btc.pubtoaddr(cj_pub, get_p2pk_vbyte()) self.outputs.append({'address': cj_addr, 'value': self.cj_amount}) self.cjfee_total += real_cjfee self.maker_txfee_contributions += self.active_orders[nick]['txfee'] self.nonrespondants.remove(nick) if len(self.nonrespondants) > 0: log.debug('nonrespondants = ' + str(self.nonrespondants)) return self.all_responded = True with self.timeout_lock: self.timeout_lock.notify() log.debug('got all parts, enough to build a tx') self.nonrespondants = list(self.active_orders.keys()) my_total_in = sum([va['value'] for u, va in self.input_utxos.iteritems()]) if self.my_change_addr: #Estimate fee per choice of next/3/6 blocks targetting. estimated_fee = estimate_tx_fee(len(sum( self.utxos.values(),[])), len(self.outputs)+2) log.debug("Based on initial guess: "+str( self.total_txfee)+", we estimated a fee of: "+str(estimated_fee)) #reset total self.total_txfee = estimated_fee my_txfee = max(self.total_txfee - self.maker_txfee_contributions, 0) my_change_value = ( my_total_in - self.cj_amount - self.cjfee_total - my_txfee) #Since we could not predict the maker's inputs, we may end up needing #too much such that the change value is negative or small. Note that #we have tried to avoid this based on over-estimating the needed amount #in SendPayment.create_tx(), but it is still a possibility if one maker #uses a *lot* of inputs. if self.my_change_addr and my_change_value <= 0: raise ValueError("Calculated transaction fee of: "+str( self.total_txfee)+" is too large for our inputs;Please try again.") elif self.my_change_addr and my_change_value <= jm_single().DUST_THRESHOLD: log.debug("Dynamically calculated change lower than dust: "+str( my_change_value)+"; dropping.") self.my_change_addr = None my_change_value = 0 log.debug('fee breakdown for me totalin=%d my_txfee=%d makers_txfee=%d cjfee_total=%d => changevalue=%d' % (my_total_in, my_txfee, self.maker_txfee_contributions, self.cjfee_total, my_change_value)) if self.my_change_addr is None: if my_change_value != 0 and abs(my_change_value) != 1: # seems you wont always get exactly zero because of integer # rounding so 1 satoshi extra or fewer being spent as miner # fees is acceptable log.debug(('WARNING CHANGE NOT BEING ' 'USED\nCHANGEVALUE = {}').format(my_change_value)) else: self.outputs.append({'address': self.my_change_addr, 'value': my_change_value}) self.utxo_tx = [dict([('output', u)]) for u in sum(self.utxos.values(), [])] self.outputs.append({'address': self.coinjoin_address(), 'value': self.cj_amount}) random.shuffle(self.utxo_tx) random.shuffle(self.outputs) tx = btc.mktx(self.utxo_tx, self.outputs) log.debug('obtained tx\n' + pprint.pformat(btc.deserialize(tx))) self.msgchan.send_tx(self.active_orders.keys(), tx) self.latest_tx = btc.deserialize(tx) for index, ins in enumerate(self.latest_tx['ins']): utxo = ins['outpoint']['hash'] + ':' + str( ins['outpoint']['index']) if utxo not in self.input_utxos.keys(): continue # placeholders required ins['script'] = 'deadbeef'