def main(): jm_single().nickname = random_nick() # watcher' +binascii.hexlify(os.urandom(4)) 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) # todo: is the call to GUITaker needed, or the return. taker unused taker = GUITaker(mcc, hostport) print('starting irc') mcc.run()
def main(): jm_single().nickname = random_nick() # watcher' +binascii.hexlify(os.urandom(4)) 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) irc = IRCMessageChannel(jm_single().nickname) # todo: is the call to GUITaker needed, or the return. taker unused taker = GUITaker(irc, hostport) print('starting irc') irc.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())
from joinmarket import Maker, IRCMessageChannel from joinmarket import BlockrInterface from joinmarket import jm_single, get_network, load_program_config from joinmarket import random_nick from joinmarket import get_log, calc_cj_fee, debug_dump_object from joinmarket import Wallet # data_dir = os.path.dirname(os.path.realpath(__file__)) # sys.path.insert(0, os.path.join(data_dir, 'joinmarket')) # import blockchaininterface txfee = 1000 cjfee = '0.002' # 0.2% fee jm_single().nickname = random_nick() nickserv_password = '' # minimum size is such that you always net profit at least 20% of the miner fee minsize = int(1.2 * txfee / float(cjfee)) mix_levels = 5 log = get_log() # is a maker for the purposes of generating a yield from held # bitcoins without ruining privacy for the taker, the taker could easily check # the history of the utxos this bot sends, so theres not much incentive # to ruin the privacy for barely any more yield # sell-side algorithm:
def main(): parser = OptionParser( usage='usage: %prog [options] [auth utxo] [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) < 3: parser.error('Needs a wallet, amount and destination address') sys.exit(0) auth_utxo = args[0] cjamount = int(args[1]) destaddr = args[2] changeaddr = args[3] cold_utxos = args[4:] 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 all_utxos = [auth_utxo] + cold_utxos query_result = jm_single().bc_interface.query_utxo_set(all_utxos) if None in query_result: print query_result utxo_data = {} for utxo, data in zip(all_utxos, query_result): utxo_data[utxo] = {'address': data['address'], 'value': data['value']} auth_privkey = raw_input('input private key for ' + utxo_data[auth_utxo]['address'] + ' :') if utxo_data[auth_utxo]['address'] != btc.privtoaddr( auth_privkey, magicbyte=get_p2pk_vbyte()): print 'ERROR: privkey does not match auth utxo' return 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 jm_single().nickname = random_nick() log.debug('starting sendpayment') class UnsignedTXWallet(AbstractWallet): def get_key_from_addr(self, addr): log.debug('getting privkey of ' + addr) if btc.privtoaddr(auth_privkey, magicbyte=get_p2pk_vbyte()) != addr: raise RuntimeError('privkey doesnt match given address') return auth_privkey wallet = UnsignedTXWallet() irc = IRCMessageChannel(jm_single().nickname) taker = CreateUnsignedTx(irc, wallet, auth_utxo, cjamount, destaddr, changeaddr, utxo_data, options, chooseOrdersFunc) try: log.debug('starting irc') irc.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=5000, help='number of satoshis per participant to use as the initial estimate '+ 'for the total transaction fee, default=5000, 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=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('-m', '--mixdepth', action='store', type='int', dest='mixdepth', help='mixing depth to spend from, default=0', default=0) 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 jm_single().nickname = random_nick() log.debug('starting sendpayment') if not options.userpcwallet: wallet = Wallet(wallet_name, options.mixdepth + 1, options.gaplimit) else: wallet = BitcoinCoreWallet(fromaccount=wallet_name) jm_single().bc_interface.sync_wallet(wallet) irc = IRCMessageChannel(jm_single().nickname) taker = SendPayment(irc, wallet, destaddr, amount, options.makercount, options.txfee, options.waittime, options.mixdepth, options.answeryes, chooseOrdersFunc) try: log.debug('starting irc') irc.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): """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 = [] 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) if btc.secp_present: destaddr = btc.privkey_to_address(os.urandom(32), from_hex=False, magicbyte=get_p2pk_vbyte()) else: destaddr = btc.privkey_to_address(os.urandom(32), 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 jm_single().nickname = random_nick() log.debug('starting sendpayment') jm_single().bc_interface.sync_wallet(wallet) #Trigger PING LAG sending artificially joinmarket.irc.PING_INTERVAL = 3 irc = IRCMessageChannel(jm_single().nickname) taker = sendpayment.SendPayment(irc, wallet, destaddr, amount, makercount, txfee, waittime, mixdepth, answeryes, chooseOrdersFunc) try: log.debug('starting irc') irc.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'] 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 main(): global txfee, cjfee_a, cjfee_r, ordertype, nickserv_password, minsize, mix_levels 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 relorder or absorder') 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('-n', '--nickname', action='store', type='string', dest='nickname', default=jm_single().nickname, help='irc nickname') 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 == 'relorder': 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 == 'absorder': if options.cjfee != '': cjfee_a = int(options.cjfee) minsize = options.minsize else: parser.error( 'You specified an incorrect order type which can be either relorder or absorder' ) sys.exit(0) if jm_single().nickname == options.nickname: jm_single().nickname = random_nick() else: jm_single().nickname = options.nickname 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) # nickname is set way above # nickname log.debug('starting yield generator') irc = IRCMessageChannel( jm_single().nickname, realname='btcint=' + jm_single().config.get("BLOCKCHAIN", "blockchain_source"), password=nickserv_password) maker = YieldGenerator(irc, wallet) try: log.debug('connecting to irc') irc.run() except: log.debug('CRASHING, DUMPING EVERYTHING') debug_dump_object(wallet, ['addr_cache', 'keys', 'seed']) debug_dump_object(maker) debug_dump_object(irc) import traceback log.debug(traceback.format_exc())
def main(): parser = OptionParser( usage= 'usage: %prog [options] [wallet file / fromaccount] [amount] [destaddr]', description='Sends a payment from your wallet to an given address' + ' using coinjoin. 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.' + ' NOTE: In the current state of JoinMarket software, this script' + ' only works if your JoinMarket wallet contains the private key of your' + ' destination address. So you can only send to yourself and you need' + ' to import the privkey') parser.add_option( '-f', '--txfee', action='store', type='int', dest='txfee', default=10000, help='miner fee contribution, in satoshis, default=10000') parser.add_option( '-N', '--makercount', action='store', type='int', dest='makercount', help= 'how many makers to coinjoin with when taking liquidity, default=2', default=2) parser.add_option( '-w', '--wait-time', action='store', type='float', dest='waittime', help='wait time in hours as a maker before becoming a taker, default=8', default=8) parser.add_option( '-c', '--cjfee', action='store', type='int', dest='cjfee', help= 'coinjoin fee asked for when being a maker, in satoshis per order filled, default=50000', default=50000) 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') (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 waittime = timedelta(hours=options.waittime).total_seconds() print 'txfee=%d cjfee=%d waittime=%s makercount=%d' % ( options.txfee, options.cjfee, str( timedelta(hours=options.waittime)), options.makercount) # 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) jm_single().bc_interface.sync_wallet(wallet) available_balance = wallet.get_balance_by_mixdepth()[options.mixdepth] if available_balance < amount: print 'not enough money at mixdepth=%d, exiting' % options.mixdepth return jm_single().nickname = random_nick() log.debug('Running patient sender of a payment') irc = IRCMessageChannel(jm_single().nickname) PatientSendPayment(irc, wallet, destaddr, amount, options.makercount, options.txfee, options.cjfee, waittime, options.mixdepth) try: irc.run() except: log.debug('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()
import ConfigParser import csv import threading import copy from joinmarket import Maker, IRCMessageChannel, OrderbookWatch from joinmarket import blockchaininterface, BlockrInterface from joinmarket import jm_single, get_network, load_program_config from joinmarket import random_nick from joinmarket import get_log, calc_cj_fee, debug_dump_object from joinmarket import Wallet config = ConfigParser.RawConfigParser() config.read('joinmarket.cfg') mix_levels = 5 nickname = random_nick() nickserv_password = '' # EXPLANATION # It watches your own transaction volume, and raises or lowers prices based # on how many transactions you are getting. # Price ranges that arent getting used will stay cheap, # while frequently used price ranges will raise themselves in price. # # CONFIGURATION # starting size: offer amount in btc # price floor: cjfee in satoshis # price increment: increase your cjfee by this much per tranaction # time frame: Transaction count is within this window. This is how long it # takes to drop to the price floor if there have been no # transactions. A short window is more aggressive at dropping
def main(): parser = OptionParser( usage= 'usage: %prog [options] [wallet file / fromaccount] [amount] [destaddr]', description='Sends a payment from your wallet to an given address' + ' using coinjoin. 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') parser.add_option( '-f', '--txfee', action='store', type='int', dest='txfee', default=10000, help='miner fee contribution, in satoshis, default=10000') parser.add_option( '-N', '--makercount', action='store', type='int', dest='makercount', help= 'how many makers to coinjoin with when taking liquidity, default=2', default=2) parser.add_option( '-w', '--wait-time', action='store', type='float', dest='waittime', help='wait time in hours as a maker before becoming a taker, default=8', default=8) parser.add_option( '-c', '--cjfee', action='store', type='int', dest='cjfee', help= 'coinjoin fee asked for when being a maker, in satoshis per order filled, default=50000', default=50000) 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') (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 waittime = timedelta(hours=options.waittime).total_seconds() print 'txfee=%d cjfee=%d waittime=%s makercount=%d' % ( options.txfee, options.cjfee, str(timedelta(hours=options.waittime)), options.makercount) # 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) jm_single().bc_interface.sync_wallet(wallet) available_balance = wallet.get_balance_by_mixdepth()[options.mixdepth] if available_balance < amount: print 'not enough money at mixdepth=%d, exiting' % options.mixdepth return jm_single().nickname = random_nick() log.debug('Running patient sender of a payment') irc = IRCMessageChannel(jm_single().nickname) PatientSendPayment(irc, wallet, destaddr, amount, options.makercount, options.txfee, options.cjfee, waittime, options.mixdepth) try: irc.run() except: log.debug('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 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 / 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=5000, help= 'number of satoshis per participant to use as the initial estimate ' + 'for the total transaction fee, default=5000, 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=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('-m', '--mixdepth', action='store', type='int', dest='mixdepth', help='mixing depth to spend from, default=0', default=0) 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 jm_single().nickname = random_nick() log.debug('starting sendpayment') if not options.userpcwallet: wallet = Wallet(wallet_name, options.mixdepth + 1, options.gaplimit) else: wallet = BitcoinCoreWallet(fromaccount=wallet_name) jm_single().bc_interface.sync_wallet(wallet) irc = IRCMessageChannel(jm_single().nickname) taker = SendPayment(irc, wallet, destaddr, amount, options.makercount, options.txfee, options.waittime, options.mixdepth, options.answeryes, chooseOrdersFunc) try: log.debug('starting irc') irc.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] [auth utxo] [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) < 3: parser.error('Needs a wallet, amount and destination address') sys.exit(0) auth_utxo = args[0] cjamount = int(args[1]) destaddr = args[2] changeaddr = args[3] cold_utxos = args[4:] 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 all_utxos = [auth_utxo] + cold_utxos query_result = jm_single().bc_interface.query_utxo_set(all_utxos) if None in query_result: print query_result utxo_data = {} for utxo, data in zip(all_utxos, query_result): utxo_data[utxo] = {'address': data['address'], 'value': data['value']} auth_privkey = raw_input('input private key for ' + utxo_data[auth_utxo][ 'address'] + ' :') if utxo_data[auth_utxo]['address'] != btc.privtoaddr( auth_privkey, get_p2pk_vbyte()): print 'ERROR: privkey does not match auth utxo' return 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 jm_single().nickname = random_nick() log.debug('starting sendpayment') class UnsignedTXWallet(AbstractWallet): def get_key_from_addr(self, addr): log.debug('getting privkey of ' + addr) if btc.privtoaddr(auth_privkey, get_p2pk_vbyte()) != addr: raise RuntimeError('privkey doesnt match given address') return auth_privkey wallet = UnsignedTXWallet() irc = IRCMessageChannel(jm_single().nickname) taker = CreateUnsignedTx(irc, wallet, auth_utxo, cjamount, destaddr, changeaddr, utxo_data, options, chooseOrdersFunc) try: log.debug('starting irc') irc.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] [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=5000, help='number of satoshis per participant to use as the initial estimate '+ 'for the total transaction fee, default=5000, 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. 3 1.5 will be a normal distribution ' 'with mean 3 and standard deveation 1.5 inclusive, default=3 1.5', default=(3, 1.5)) parser.add_option( '--minmakercount', type='int', dest='minmakercount', default=2, help= 'The minimum maker count in a transaction, random values below this are clamped at this number. default=2') 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') (options, args) = parser.parse_args() 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 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().nickname = random_nick() log.debug('starting tumbler') irc = IRCMessageChannel(jm_single().nickname) tumbler = Tumbler(irc, wallet, tx_list, options) try: log.debug('connecting to irc') irc.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(): global txfee, cjfee_a, cjfee_r, ordertype, nickserv_password, minsize, mix_levels 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 relorder or absorder') 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('-n', '--nickname', action='store', type='string', dest='nickname', default=jm_single().nickname, help='irc nickname') 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 == 'relorder': 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 == 'absorder': if options.cjfee != '': cjfee_a = int(options.cjfee) minsize = options.minsize else: parser.error('You specified an incorrect order type which can be either relorder or absorder') sys.exit(0) if jm_single().nickname == options.nickname: jm_single().nickname = random_nick() else: jm_single().nickname = options.nickname 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) # nickname is set way above # nickname log.debug('starting yield generator') irc = IRCMessageChannel(jm_single().nickname, realname='btcint=' + jm_single().config.get( "BLOCKCHAIN", "blockchain_source"), password=nickserv_password) maker = YieldGenerator(irc, wallet) try: log.debug('connecting to irc') irc.run() except: log.debug('CRASHING, DUMPING EVERYTHING') debug_dump_object(wallet, ['addr_cache', 'keys', 'seed']) debug_dump_object(maker) debug_dump_object(irc) import traceback log.debug(traceback.format_exc())
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=5000, help= 'number of satoshis per participant to use as the initial estimate ' + 'for the total transaction fee, default=5000, 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. 3 1.5 will be a normal distribution ' 'with mean 3 and standard deveation 1.5 inclusive, default=3 1.5', default=(3, 1.5)) parser.add_option( '--minmakercount', type='int', dest='minmakercount', default=2, help= 'The minimum maker count in a transaction, random values below this are clamped at this number. default=2' ) 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() 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 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().nickname = random_nick() log.debug('starting tumbler') irc = IRCMessageChannel(jm_single().nickname) tumbler = Tumbler(irc, wallet, tx_list, options) try: log.debug('connecting to irc') irc.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 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): if btc.secp_present: destaddr = btc.privkey_to_address( os.urandom(32), from_hex=False, magicbyte=get_p2pk_vbyte()) else: destaddr = btc.privkey_to_address( os.urandom(32), 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 irc = IRCMessageChannel(jm_single().nickname) tumbler_bot = tumbler.Tumbler(irc, wallet, tx_list, options) try: log.debug('starting irc') irc.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
import time import binascii import sys import random import decimal from joinmarket import Maker, IRCMessageChannel from joinmarket import blockchaininterface, BlockrInterface from joinmarket import jm_single, get_network, load_program_config from joinmarket import random_nick from joinmarket import get_log, calc_cj_fee, debug_dump_object from joinmarket import Wallet #CONFIGURATION mix_levels = 5 # Careful! Only change this if you setup your wallet as such. nickname = random_nick() nickserv_password = '' #Spread Types # fibonacci- will gradually increase at the rate of the fibonacci sequence # evenly- will be evenly spaced # random- random amounts between the high and the low # custom- use _custom to set it directly # bymixdepth- (for offers), make offer amounts equal to mixdepths # note, when using bymixdepth, set 'num_offers = mix_levels' # min and max offer sizes offer_spread = 'fibonacci' # fibonacci, evenly, random, custom, bymixdepth offer_low = None # satoshis. when None, min_output_size will be used offer_high = None # satoshis. when None, size of largest mix depth will be used #offer_low = random.randrange(21000000, 1e8) #random
def test_sendpayment(setup_regtest, num_ygs, wallet_structures, mean_amt, mixdepth, sending_amt): """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 = [] 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) if btc.secp_present: destaddr = btc.privkey_to_address( os.urandom(32), from_hex=False, magicbyte=get_p2pk_vbyte()) else: destaddr = btc.privkey_to_address( os.urandom(32), 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 jm_single().nickname = random_nick() log.debug('starting sendpayment') jm_single().bc_interface.sync_wallet(wallet) #Trigger PING LAG sending artificially joinmarket.irc.PING_INTERVAL = 3 irc = IRCMessageChannel(jm_single().nickname) taker = sendpayment.SendPayment(irc, wallet, destaddr, amount, makercount, txfee, waittime, mixdepth, answeryes, chooseOrdersFunc) try: log.debug('starting irc') irc.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'] 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