def validate_utxo_data(utxo_datas, retrieve=False): """For each txid: N, privkey, first convert the privkey and convert to address, then use the blockchain instance to look up the utxo and check that its address field matches. If retrieve is True, return the set of utxos and their values. """ results = [] for u, priv in utxo_datas: print 'validating this utxo: ' + str(u) hexpriv = btc.from_wif_privkey(priv, vbyte=get_p2pk_vbyte()) addr = btc.privkey_to_address(hexpriv, magicbyte=get_p2pk_vbyte()) print 'claimed address: ' + addr res = jm_single().bc_interface.query_utxo_set([u]) print 'blockchain shows this data: ' + str(res) if len(res) != 1: print "utxo not found on blockchain: " + str(u) return False if res[0]['address'] != addr: print "privkey corresponds to the wrong address for utxo: " + str(u) print "blockchain returned address: " + res[0]['address'] print "your privkey gave this address: " + addr return False if retrieve: results.append((u, res[0]['value'])) print 'all utxos validated OK' if retrieve: return results return True
def test_p2p_broadcast(setup_tx_notify): #listen up kids, dont do this to generate private #keys that hold real money, or else you'll be robbed src_privkey = random.getrandbits(256) src_privkey = btc.encode(src_privkey, 16, 64) + '01' src_addr = btc.privtoaddr(src_privkey, magicbyte=get_p2pk_vbyte()) dst_addr = btc.pubtoaddr('03' + btc.encode(random.getrandbits(256), 16), get_p2pk_vbyte()) jm_single().bc_interface.rpc('importaddress', [src_addr, "", False]) jm_single().bc_interface.rpc('importaddress', [dst_addr, "", False]) jm_single().bc_interface.rpc('generatetoaddress', [1, src_addr]) jm_single().bc_interface.rpc('generate', [101]) src_utxos = jm_single().bc_interface.rpc('listunspent', [0, 500, [src_addr]]) inputs = [{ 'output': src_utxos[0]['txid'] + ':' + str(src_utxos[0]['vout']) }] miner_fee = 10000 outs = [{ 'address': dst_addr, 'value': int(src_utxos[0]['amount'] * 1e8) - miner_fee }] tx = btc.mktx(inputs, outs) tx = btc.sign(tx, 0, src_privkey) bad_tx = random.getrandbits(len(tx) * 4) bad_tx = btc.encode(bad_tx, 16, len(tx)) utxo_before = jm_single().bc_interface.rpc('listunspent', [0, 500, [dst_addr]]) #jm_single().bc_interface.rpc('sendrawtransaction', [tx]) pushed = tor_broadcast_tx(tx, None, 'regtest', remote_hostport=('localhost', 18444)) assert pushed pushed = tor_broadcast_tx(tx, None, 'regtest', remote_hostport=('localhost', 18444)) assert not pushed #node should already have the same tx, reject pushed = tor_broadcast_tx(bad_tx, None, 'regtest', remote_hostport=('localhost', 18444)) assert not pushed #bad tx should be rejected jm_single().bc_interface.rpc('generate', [1]) utxo_after = jm_single().bc_interface.rpc('listunspent', [0, 500, [dst_addr]]) return len(utxo_after) - 1 == len(utxo_before)
def run_send(self, bad=False): yigen_procs = [] if bad: i = 2 else: i = 0 ygp = local_command([python_cmd,yg_cmd,\ str(self.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) #run a single sendpayment call amt = 100000000 #in satoshis dest_address = btc.privkey_to_address(os.urandom(32), get_p2pk_vbyte()) try: sp_proc = local_command([python_cmd, 'sendpayment.py', '--yes', '-N', '1', self.wallets[1]['seed'], str( amt), dest_address]) except subprocess.CalledProcessError, e: for ygp in yigen_procs: ygp.kill() print e.returncode print e.message raise
def run_tumble(self, amt): yigen_procs = [] for i in range(6): ygp = local_command([python_cmd, yg_cmd,\ str(self.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) #start a tumbler amt = amt * 1e8 #in satoshis #send to any old address dest_address = btc.privkey_to_address(os.urandom(32), get_p2pk_vbyte()) try: print 'taker seed: ' + self.wallets[6]['seed'] while True: print 'hello' time.sleep(80) except subprocess.CalledProcessError, e: for ygp in yigen_procs: ygp.kill() print e.returncode print e.message raise
def test_direct_send(setup_regtest, wallet_structures, mean_amt, mixdepth, amount, valid): log = get_log() wallets = make_wallets(1, wallet_structures=wallet_structures, mean_amt=mean_amt) wallet = wallets[0]['wallet'] sync_wallet(wallet) destaddr = btc.privkey_to_address( os.urandom(32), #TODO deterministic-ise from_hex=False, magicbyte=get_p2pk_vbyte()) addr_valid, errormsg = validate_address(destaddr) assert addr_valid, "Invalid destination address: " + destaddr + \ ", error message: " + errormsg if not valid: with pytest.raises(Exception) as e_info: sendpayment.direct_send(wallet, amount, mixdepth, destaddr, answeryes=True) else: sendpayment.direct_send(wallet, amount, mixdepth, destaddr, answeryes=True)
def run_tumble(self, amt): yigen_procs = [] for i in range(6): ygp = local_command([python_cmd, yg_cmd,\ str(self.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) #start a tumbler amt = amt * 1e8 #in satoshis #send to any old address dest_address = btc.privkey_to_address(os.urandom(32), get_p2pk_vbyte()) try: print 'taker seed: '+self.wallets[6]['seed'] while True: print 'hello' time.sleep(80) except subprocess.CalledProcessError, e: for ygp in yigen_procs: ygp.kill() print e.returncode print e.message raise
def run_send(self, bad=False): yigen_procs = [] if bad: i = 2 else: i = 0 ygp = local_command([python_cmd,yg_cmd,\ str(self.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) #run a single sendpayment call amt = 100000000 #in satoshis dest_address = btc.privkey_to_address(os.urandom(32), get_p2pk_vbyte()) try: sp_proc = local_command([ python_cmd, 'sendpayment.py', '--yes', '-N', '1', self.wallets[1]['seed'], str(amt), dest_address ]) except subprocess.CalledProcessError, e: for ygp in yigen_procs: ygp.kill() print e.returncode print e.message raise
def test_blockr_sync(setup_blockr, net, seed, gaplimit, showprivkey, method): jm_single().config.set("BLOCKCHAIN", "network", net) wallet = Wallet(seed, max_mix_depth=5) jm_single().bc_interface.sync_wallet(wallet) #copy pasted from wallet-tool; some boiled down form of #this should really be in wallet.py in the joinmarket module. def cus_print(s): print s total_balance = 0 for m in range(wallet.max_mix_depth): cus_print('mixing depth %d m/0/%d/' % (m, m)) balance_depth = 0 for forchange in [0, 1]: cus_print(' ' + ('external' if forchange == 0 else 'internal') + ' addresses m/0/%d/%d/' % (m, forchange)) for k in range(wallet.index[m][forchange] + gaplimit): addr = wallet.get_addr(m, forchange, k) balance = 0.0 for addrvalue in wallet.unspent.values(): if addr == addrvalue['address']: balance += addrvalue['value'] balance_depth += balance used = ('used' if k < wallet.index[m][forchange] else ' new') if showprivkey: if btc.secp_present: privkey = btc.wif_compressed_privkey( wallet.get_key(m, forchange, k), get_p2pk_vbyte()) else: privkey = btc.encode_privkey( wallet.get_key(m, forchange, k), 'wif_compressed', get_p2pk_vbyte()) else: privkey = '' if (method == 'displayall' or balance > 0 or (used == ' new' and forchange == 0)): cus_print( ' m/0/%d/%d/%03d %-35s%s %.8f btc %s' % (m, forchange, k, addr, used, balance / 1e8, privkey)) total_balance += balance_depth print('for mixdepth=%d balance=%.8fbtc' % (m, balance_depth / 1e8)) assert total_balance == 96143257
def test_blockr_sync(setup_blockr, net, seed, gaplimit, showprivkey, method): jm_single().config.set("BLOCKCHAIN", "network", net) wallet = Wallet(seed, max_mix_depth = 5) jm_single().bc_interface.sync_wallet(wallet) #copy pasted from wallet-tool; some boiled down form of #this should really be in wallet.py in the joinmarket module. def cus_print(s): print s total_balance = 0 for m in range(wallet.max_mix_depth): cus_print('mixing depth %d m/0/%d/' % (m, m)) balance_depth = 0 for forchange in [0, 1]: cus_print(' ' + ('external' if forchange == 0 else 'internal') + ' addresses m/0/%d/%d/' % (m, forchange)) for k in range(wallet.index[m][forchange] + gaplimit): addr = wallet.get_addr(m, forchange, k) balance = 0.0 for addrvalue in wallet.unspent.values(): if addr == addrvalue['address']: balance += addrvalue['value'] balance_depth += balance used = ('used' if k < wallet.index[m][forchange] else ' new') if showprivkey: if btc.secp_present: privkey = btc.wif_compressed_privkey( wallet.get_key(m, forchange, k), get_p2pk_vbyte()) else: privkey = btc.encode_privkey(wallet.get_key(m, forchange, k), 'wif_compressed', get_p2pk_vbyte()) else: privkey = '' if (method == 'displayall' or balance > 0 or (used == ' new' and forchange == 0)): cus_print(' m/0/%d/%d/%03d %-35s%s %.8f btc %s' % (m, forchange, k, addr, used, balance / 1e8, privkey)) total_balance += balance_depth print('for mixdepth=%d balance=%.8fbtc' % (m, balance_depth / 1e8)) assert total_balance == 96143257
def generate_single_podle_sig(u, priv, i): """Make a podle entry for key priv at index i, using a dummy utxo value. This calls the underlying 'raw' code based on the class PoDLE, not the library 'generate_podle' which intelligently searches and updates commitments. """ #Convert priv to hex hexpriv = btc.from_wif_privkey(priv, vbyte=get_p2pk_vbyte()) podle = btc.PoDLE(u, hexpriv) r = podle.generate_podle(i) return (r['P'], r['P2'], r['sig'], r['e'], r['commit'])
def get_next_address(send_job): if 'addresses' in send_job: this_index = send_job['index'] send_job['index'] = (send_job['index'] + 1) % len(send_job['addresses']) return send_job['addresses'][this_index] elif 'xpub' in send_job: send_job['index'] += 1 return btc.pubtoaddr(btc.bip32_extract_key(btc.bip32_ckd( send_job['xpub'], send_job['index']-1)), get_p2pk_vbyte()) else: assert False
def donation_address(tx, wallet): from bitcoin.main import multiply, G, deterministic_generate_k, add_pubkeys reusable_donation_pubkey = ('02be838257fbfddabaea03afbb9f16e852' '9dfe2de921260a5c46036d97b5eacf2a') privkey = wallet.get_key_from_addr(wallet.get_new_addr(0, 0)) msghash = btc.bin_txhash(tx, btc.SIGHASH_ALL) # generate unpredictable k global sign_k sign_k = deterministic_generate_k(msghash, privkey) c = btc.sha256(multiply(reusable_donation_pubkey, sign_k)) sender_pubkey = add_pubkeys(reusable_donation_pubkey, multiply(G, c)) sender_address = btc.pubtoaddr(sender_pubkey, get_p2pk_vbyte()) log.debug('sending coins to ' + sender_address) return privkey, sender_address
def donation_address(cjtx, wallet): privkey = wallet.get_key_from_addr(wallet.get_new_addr(0,0)) reusable_donation_pubkey = '02be838257fbfddabaea03afbb9f16e8529dfe2de921260a5c46036d97b5eacf2a' global sign_k import os import binascii sign_k = os.urandom(32) log.debug("Using the following nonce value: "+binascii.hexlify(sign_k)) c = btc.sha256(btc.multiply(binascii.hexlify(sign_k), reusable_donation_pubkey, True)) sender_pubkey = btc.add_pubkeys([reusable_donation_pubkey, btc.privtopub(c+'01', True)], True) sender_address = btc.pubtoaddr(sender_pubkey, get_p2pk_vbyte()) log.debug('sending coins to ' + sender_address) return privkey, sender_address
def donation_address(tx, wallet): from bitcoin.main import multiply, G, deterministic_generate_k, add_pubkeys reusable_donation_pubkey = ('02be838257fbfddabaea03afbb9f16e852' '9dfe2de921260a5c46036d97b5eacf2a') privkey = wallet.get_key_from_addr(wallet.get_new_addr(0,0)) msghash = btc.bin_txhash(tx, btc.SIGHASH_ALL) # generate unpredictable k global sign_k sign_k = deterministic_generate_k(msghash, privkey) c = btc.sha256(multiply(reusable_donation_pubkey, sign_k)) sender_pubkey = add_pubkeys( reusable_donation_pubkey, multiply( G, c)) sender_address = btc.pubtoaddr(sender_pubkey, get_p2pk_vbyte()) log.debug('sending coins to ' + sender_address) return privkey, sender_address
def donation_address(cjtx, wallet): privkey = wallet.get_key_from_addr(wallet.get_new_addr(0, 0)) reusable_donation_pubkey = '02be838257fbfddabaea03afbb9f16e8529dfe2de921260a5c46036d97b5eacf2a' global sign_k import os import binascii sign_k = os.urandom(32) log.debug("Using the following nonce value: " + binascii.hexlify(sign_k)) c = btc.sha256( btc.multiply(binascii.hexlify(sign_k), reusable_donation_pubkey, True)) sender_pubkey = btc.add_pubkeys( [reusable_donation_pubkey, btc.privtopub(c + '01', True)], True) sender_address = btc.pubtoaddr(sender_pubkey, get_p2pk_vbyte()) log.debug('sending coins to ' + sender_address) return privkey, sender_address
def get_utxo_info(upriv): """Verify that the input string parses correctly as (utxo, priv) and return that. """ try: u, priv = upriv.split(',') u = u.strip() priv = priv.strip() txid, n = u.split(':') assert len(txid)==64 assert len(n) in range(1, 4) n = int(n) assert n in range(256) except: #not sending data to stdout in case privkey info print "Failed to parse utxo information for utxo" try: hexpriv = btc.from_wif_privkey(priv, vbyte=get_p2pk_vbyte()) except: print "failed to parse privkey, make sure it's WIF compressed format." return u, priv
def run_simple_send(self, n, m): #start yield generator with wallet1 yigen_proc = local_command( [python_cmd, yg_cmd, str(self.wallets[0]['seed'])], bg=True) #A significant delay is needed to wait for the yield generator to sync its wallet time.sleep(20) #run a single sendpayment call with wallet2 amt = n * 100000000 #in satoshis dest_address = btc.privkey_to_address(os.urandom(32), get_p2pk_vbyte()) try: for i in range(m): sp_proc = local_command([python_cmd,'sendpayment.py','--yes','-N','1', self.wallets[1]['seed'],\ str(amt), dest_address]) except subprocess.CalledProcessError, e: if yigen_proc: yigen_proc.terminate() print e.returncode print e.message raise
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 run_sweep(self): #currently broken due to flooding; to make it work #change the 60 loop for the bad wallet to 20 yigen_procs = [] ygp = local_command([python_cmd,yg_cmd,\ str(self.wallets[3]['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) dest_address = btc.privkey_to_address(os.urandom(32), get_p2pk_vbyte()) try: sp_proc = local_command([python_cmd, 'sendpayment.py', '--yes', '-N', '1', self.wallets[2][ 'seed'], '0', dest_address]) except subprocess.CalledProcessError, e: for ygp in yigen_procs: ygp.kill() print e.returncode print e.message raise
def run_tumble(self, amt): yigen_procs = [] for i in range(6): ygp = local_command([python_cmd, yg_cmd,\ str(self.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(60) #start a tumbler amt = amt * 1e8 #in satoshis #send to any old address dest_address = btc.privkey_to_address(os.urandom(32), get_p2pk_vbyte()) try: #default mixdepth source is zero, so will take coins from m 0. #see tumbler.py --h for details expected = ['tumble with these tx'] test_in = ['y'] p = pexpect.spawn(python_cmd, ['tumbler.py', '-N', '2', '0', '-a', '0', '-M', '5', '-w', '10', '-l', '0.2', '-s', '1000000', '-q', '5', self.wallets[6]['seed'], dest_address]) interact(p, test_in, expected) p.expect(pexpect.EOF, timeout=100000) p.close() if p.exitstatus != 0: print 'failed due to exit status: ' + str(p.exitstatus) return False except subprocess.CalledProcessError, e: for ygp in yigen_procs: ygp.kill() print e.returncode print e.message raise
def run_sweep(self): #currently broken due to flooding; to make it work #change the 60 loop for the bad wallet to 20 yigen_procs = [] ygp = local_command([python_cmd,yg_cmd,\ str(self.wallets[3]['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) dest_address = btc.privkey_to_address(os.urandom(32), get_p2pk_vbyte()) try: sp_proc = local_command([ python_cmd, 'sendpayment.py', '--yes', '-N', '1', self.wallets[2]['seed'], '0', dest_address ]) except subprocess.CalledProcessError, e: for ygp in yigen_procs: ygp.kill() print e.returncode print e.message raise
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 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 test_donation_address(setup_donations, amount): wallets = make_wallets(1, wallet_structures=[[1, 1, 1, 0, 0]], mean_amt=0.5) wallet = wallets[0]['wallet'] jm_single().bc_interface.sync_wallet(wallet) #make a rdp from a simple privkey rdp_priv = "\x01" * 32 reusable_donation_pubkey = binascii.hexlify( secp256k1.PrivateKey(privkey=rdp_priv, raw=True, ctx=btc.ctx).pubkey.serialize()) dest_addr, sign_k = donation_address(reusable_donation_pubkey) print dest_addr jm_single().bc_interface.rpc('importaddress', [dest_addr, '', False]) ins_full = wallet.unspent total = sum(x['value'] for x in ins_full.values()) ins = ins_full.keys() output_addr = wallet.get_new_addr(1, 1) fee_est = 10000 outs = [{ 'value': amount, 'address': dest_addr }, { 'value': total - amount - fee_est, 'address': output_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) priv = binascii.unhexlify(priv) usenonce = binascii.unhexlify(sign_k) if index == 0 else None if index == 0: log.debug("Applying rdp to input: " + str(ins)) tx = btc.sign(tx, index, priv, usenonce=usenonce) #pushtx returns False on any error push_succeed = jm_single().bc_interface.pushtx(tx) if push_succeed: log.debug(btc.txhash(tx)) else: assert False #Role of receiver: regenerate the destination private key, #and address, from the nonce of the first input; check it has #received the coins. detx = btc.deserialize(tx) first_utxo_script = detx['ins'][0]['script'] sig, pub = btc.deserialize_script(first_utxo_script) log.debug(sig) sig = binascii.unhexlify(sig) kGlen = ord(sig[3]) kG = sig[4:4 + kGlen] log.debug(binascii.hexlify(kG)) if kG[0] == "\x00": kG = kG[1:] #H(rdp private key * K) + rdp should be ==> dest addr #Open issue: re-introduce recovery without ECC shenanigans #Just cheat by trying both signs for pubkey coerced_kG_1 = "02" + binascii.hexlify(kG) coerced_kG_2 = "03" + binascii.hexlify(kG) for coerc in [coerced_kG_1, coerced_kG_2]: c = btc.sha256(btc.multiply(binascii.hexlify(rdp_priv), coerc, True)) pub_check = btc.add_pubkeys( [reusable_donation_pubkey, btc.privtopub(c + '01', True)], True) addr_check = btc.pubtoaddr(pub_check, get_p2pk_vbyte()) log.debug("Found checked address: " + addr_check) if addr_check == dest_addr: time.sleep(3) received = jm_single().bc_interface.get_received_by_addr( [dest_addr], None)['data'][0]['balance'] assert received == amount return assert False
def test_failed_sendpayment(setup_podle, num_ygs, wallet_structures, mean_amt, mixdepth, sending_amt): """Test of initiating joins, but failing to complete, to see commitment usage. YGs in background as per test_regtest. Use sweeps to avoid recover_from_nonrespondants without intruding into sendpayment code. """ makercount = num_ygs answeryes = True txfee = 5000 waittime = 3 #Don't want to wait too long, but must account for possible #throttling with !auth jm_single().maker_timeout_sec = 12 amount = 0 wallets = make_wallets(makercount + 1, wallet_structures=wallet_structures, mean_amt=mean_amt) #the sendpayment bot uses the last wallet in the list wallet = wallets[makercount]['wallet'] yigen_procs = [] for i in range(makercount): ygp = local_command([python_cmd, yg_cmd,\ str(wallets[i]['seed'])], bg=True) time.sleep(2) #give it a chance yigen_procs.append(ygp) #A significant delay is needed to wait for the yield generators to sync time.sleep(20) destaddr = btc.privkey_to_address(os.urandom(32), from_hex=False, magicbyte=get_p2pk_vbyte()) addr_valid, errormsg = validate_address(destaddr) assert addr_valid, "Invalid destination address: " + destaddr + \ ", error message: " + errormsg #TODO paramatetrize this as a test variable chooseOrdersFunc = weighted_order_choose log.debug('starting sendpayment') jm_single().bc_interface.sync_wallet(wallet) #Trigger PING LAG sending artificially joinmarket.irc.PING_INTERVAL = 3 mcs = [IRCMessageChannel(c) for c in get_irc_mchannels()] mcc = MessageChannelCollection(mcs) #Allow taker more retries than makers allow, so as to trigger #blacklist failure case jm_single().config.set("POLICY", "taker_utxo_retries", "4") #override ioauth receipt with a dummy do-nothing callback: def on_ioauth(*args): log.debug("Taker received: " + ','.join([str(x) for x in args])) class DummySendPayment(sendpayment.SendPayment): def __init__(self, msgchan, wallet, destaddr, amount, makercount, txfee, waittime, mixdepth, answeryes, chooseOrdersFunc, on_ioauth): self.on_ioauth = on_ioauth self.podle_fails = 0 self.podle_allowed_fails = 3 #arbitrary; but do it more than once self.retries = 0 super(DummySendPayment, self).__init__(msgchan, wallet, destaddr, amount, makercount, txfee, waittime, mixdepth, answeryes, chooseOrdersFunc) def on_welcome(self): Taker.on_welcome(self) DummyPaymentThread(self).start() class DummyPaymentThread(sendpayment.PaymentThread): def finishcallback(self, coinjointx): #Don't ignore makers and just re-start self.taker.retries += 1 if self.taker.podle_fails == self.taker.podle_allowed_fails: self.taker.msgchan.shutdown() return self.create_tx() def create_tx(self): try: super(DummyPaymentThread, self).create_tx() except btc.PoDLEError: log.debug("Got one commit failure, continuing") self.taker.podle_fails += 1 taker = DummySendPayment(mcc, wallet, destaddr, amount, makercount, txfee, waittime, mixdepth, answeryes, chooseOrdersFunc, on_ioauth) try: log.debug('starting message channels') mcc.run() finally: if any(yigen_procs): for ygp in yigen_procs: #NB *GENTLE* shutdown is essential for #test coverage reporting! ygp.send_signal(signal.SIGINT) ygp.wait() #We should have been able to try (tur -1) + podle_allowed_fails times assert taker.retries == jm_single().config.getint( "POLICY", "taker_utxo_retries") + taker.podle_allowed_fails #wait for block generation time.sleep(2) received = jm_single().bc_interface.get_received_by_addr( [destaddr], None)['data'][0]['balance'] #Sanity check no transaction succeeded assert received == 0
def main(): parser = OptionParser( usage='usage: %prog [options] [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 test_donation_address(setup_donations, amount): wallets = make_wallets(1, wallet_structures=[[1,1,1,0,0]], mean_amt=0.5) wallet = wallets[0]['wallet'] jm_single().bc_interface.sync_wallet(wallet) #make a rdp from a simple privkey rdp_priv = "\x01"*32 reusable_donation_pubkey = binascii.hexlify(secp256k1.PrivateKey( privkey=rdp_priv, raw=True, ctx=btc.ctx).pubkey.serialize()) dest_addr, sign_k = donation_address(reusable_donation_pubkey) print dest_addr jm_single().bc_interface.rpc('importaddress', [dest_addr, '', False]) ins_full = wallet.unspent total = sum(x['value'] for x in ins_full.values()) ins = ins_full.keys() output_addr = wallet.get_new_addr(1, 1) fee_est = 10000 outs = [{'value': amount, 'address': dest_addr}, {'value': total - amount - fee_est, 'address': output_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) priv = binascii.unhexlify(priv) usenonce = binascii.unhexlify(sign_k) if index == 0 else None if index == 0: log.debug("Applying rdp to input: " + str(ins)) tx = btc.sign(tx, index, priv, usenonce=usenonce) #pushtx returns False on any error push_succeed = jm_single().bc_interface.pushtx(tx) if push_succeed: log.debug(btc.txhash(tx)) else: assert False #Role of receiver: regenerate the destination private key, #and address, from the nonce of the first input; check it has #received the coins. detx = btc.deserialize(tx) first_utxo_script = detx['ins'][0]['script'] sig, pub = btc.deserialize_script(first_utxo_script) log.debug(sig) sig = binascii.unhexlify(sig) kGlen = ord(sig[3]) kG = sig[4:4+kGlen] log.debug(binascii.hexlify(kG)) if kG[0] == "\x00": kG = kG[1:] #H(rdp private key * K) + rdp should be ==> dest addr #Open issue: re-introduce recovery without ECC shenanigans #Just cheat by trying both signs for pubkey coerced_kG_1 = "02" + binascii.hexlify(kG) coerced_kG_2 = "03" + binascii.hexlify(kG) for coerc in [coerced_kG_1, coerced_kG_2]: c = btc.sha256(btc.multiply(binascii.hexlify(rdp_priv), coerc, True)) pub_check = btc.add_pubkeys([reusable_donation_pubkey, btc.privtopub(c+'01', True)], True) addr_check = btc.pubtoaddr(pub_check, get_p2pk_vbyte()) log.debug("Found checked address: " + addr_check) if addr_check == dest_addr: time.sleep(3) received = jm_single().bc_interface.get_received_by_addr( [dest_addr], None)['data'][0]['balance'] assert received == amount return assert False
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
def main(): parser = OptionParser( usage= 'usage: %prog [options] [txid:n]', description="Adds one or more utxos to the list that can be used to make " "commitments for anti-snooping. Note that this utxo, and its " "PUBkey, will be revealed to makers, so consider the privacy " "implication. " "It may be useful to those who are having trouble making " "coinjoins due to several unsuccessful attempts (especially " "if your joinmarket wallet is new). " "'Utxo' means unspent transaction output, it must not " "already be spent. " "The options -w, -r and -R offer ways to load these utxos " "from a file or wallet. " "If you enter a single utxo without these options, you will be " "prompted to enter the private key here - it must be in " "WIF compressed format. " "BE CAREFUL about handling private keys! " "Don't do this in insecure environments. " "Also note this ONLY works for standard (p2pkh) utxos." ) parser.add_option( '-r', '--read-from-file', action='store', type='str', dest='in_file', help='name of plain text csv file containing utxos, one per line, format: ' 'txid:N, WIF-compressed-privkey' ) parser.add_option( '-R', '--read-from-json', action='store', type='str', dest='in_json', help='name of json formatted file containing utxos with private keys, as ' 'output from "python wallet-tool.py -u -p walletname showutxos"' ) parser.add_option( '-w', '--load-wallet', action='store', type='str', dest='loadwallet', help='name of wallet from which to load utxos and use as commitments.' ) parser.add_option( '-g', '--gap-limit', action='store', type='int', dest='gaplimit', default = 6, help='Only to be used with -w; gap limit for Joinmarket wallet, default 6.' ) parser.add_option( '-M', '--max-mixdepth', action='store', type='int', dest='maxmixdepth', default=5, help='Only to be used with -w; number of mixdepths for wallet, default 5.' ) parser.add_option( '-d', '--delete-external', action='store_true', dest='delete_ext', help='deletes the current list of external commitment utxos', default=False ) parser.add_option( '-v', '--validate-utxos', action='store_true', dest='validate', help='validate the utxos and pubkeys provided against the blockchain', default=False ) parser.add_option( '-o', '--validate-only', action='store_true', dest='vonly', help='only validate the provided utxos (file or command line), not add', default=False ) (options, args) = parser.parse_args() load_program_config() utxo_data = [] if options.delete_ext: other = options.in_file or options.in_json or options.loadwallet if len(args) > 0 or other: if raw_input("You have chosen to delete commitments, other arguments " "will be ignored; continue? (y/n)") != 'y': print "Quitting" sys.exit(0) c, e = btc.get_podle_commitments() print pformat(e) if raw_input( "You will remove the above commitments; are you sure? (y/n): ") != 'y': print "Quitting" sys.exit(0) btc.update_commitments(external_to_remove=e) print "Commitments deleted." sys.exit(0) #Three options (-w, -r, -R) for loading utxo and privkey pairs from a wallet, #csv file or json file. if options.loadwallet: os.chdir('..') #yuck (see earlier comment about package) wallet = Wallet(options.loadwallet, options.maxmixdepth, options.gaplimit) os.chdir(os.path.join(os.getcwd(), 'cmttools')) jm_single().bc_interface.sync_wallet(wallet) unsp = {} for u, av in wallet.unspent.iteritems(): addr = av['address'] key = wallet.get_key_from_addr(addr) wifkey = btc.wif_compressed_privkey(key, vbyte=get_p2pk_vbyte()) unsp[u] = {'address': av['address'], 'value': av['value'], 'privkey': wifkey} for u, pva in unsp.iteritems(): utxo_data.append((u, pva['privkey'])) elif options.in_file: with open(options.in_file, "rb") as f: utxo_info = f.readlines() for ul in utxo_info: ul = ul.rstrip() if ul: u, priv = get_utxo_info(ul) if not u: quit(parser, "Failed to parse utxo info: " + str(ul)) utxo_data.append((u, priv)) elif options.in_json: if not os.path.isfile(options.in_json): print "File: " + options.in_json + " not found." sys.exit(0) with open(options.in_json, "rb") as f: try: utxo_json = json.loads(f.read()) except: print "Failed to read json from " + options.in_json sys.exit(0) for u, pva in utxo_json.iteritems(): utxo_data.append((u, pva['privkey'])) elif len(args) == 1: u = args[0] priv = raw_input( 'input private key for ' + u + ', in WIF compressed format : ') u, priv = get_utxo_info(','.join([u, priv])) if not u: quit(parser, "Failed to parse utxo info: " + u) utxo_data.append((u, priv)) else: quit(parser, 'Invalid syntax') if options.validate or options.vonly: if not validate_utxo_data(utxo_data): quit(parser, "Utxos did not validate, quitting") if options.vonly: sys.exit(0) #We are adding utxos to the external list assert len(utxo_data) add_external_commitments(utxo_data)
balance_depth = 0 for forchange in [0, 1]: cus_print(' ' + ('external' if forchange == 0 else 'internal') + ' addresses m/0/%d/%d/' % (m, forchange)) for k in range(wallet.index[m][forchange] + options.gaplimit): addr = wallet.get_addr(m, forchange, k) balance = 0.0 for addrvalue in wallet.unspent.values(): if addr == addrvalue['address']: balance += addrvalue['value'] balance_depth += balance used = ('used' if k < wallet.index[m][forchange] else ' new') privkey = btc.encode_privkey( wallet.get_key(m, forchange, k), 'wif_compressed', get_p2pk_vbyte()) if options.showprivkey else '' if (method == 'displayall' or balance > 0 or (used == ' new' and forchange == 0)): cus_print(' m/0/%d/%d/%03d %-35s%s %.8f btc %s' % (m, forchange, k, addr, used, balance / 1e8, privkey)) if m in wallet.imported_privkeys: cus_print(' import addresses') for privkey in wallet.imported_privkeys[m]: addr = btc.privtoaddr(privkey, get_p2pk_vbyte()) balance = 0.0 for addrvalue in wallet.unspent.values(): if addr == addrvalue['address']: balance += addrvalue['value'] used = (' used' if balance > 0.0 else 'empty') balance_depth += balance
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 test_sendpayment(setup_regtest, num_ygs, wallet_structures, mean_amt, mixdepth, sending_amt, ygcfs, fails, donate): """Test of sendpayment code, with yield generators in background. """ log = get_log() makercount = num_ygs answeryes = True txfee = 5000 waittime = 5 amount = sending_amt wallets = make_wallets(makercount + 1, wallet_structures=wallet_structures, mean_amt=mean_amt) #the sendpayment bot uses the last wallet in the list wallet = wallets[makercount]['wallet'] yigen_procs = [] if ygcfs: assert makercount == len(ygcfs) for i in range(makercount): if ygcfs: #back up default config, overwrite before start os.rename("joinmarket.cfg", "joinmarket.cfg.bak") shutil.copy2(ygcfs[i], "joinmarket.cfg") ygp = local_command([python_cmd, yg_cmd,\ str(wallets[i]['seed'])], bg=True) time.sleep(2) #give it a chance yigen_procs.append(ygp) if ygcfs: #Note: in case of using multiple configs, #the starting config is what is used by sendpayment os.rename("joinmarket.cfg.bak", "joinmarket.cfg") #A significant delay is needed to wait for the yield generators to sync time.sleep(20) if donate: destaddr = None else: destaddr = btc.privkey_to_address(os.urandom(32), from_hex=False, magicbyte=get_p2pk_vbyte()) addr_valid, errormsg = validate_address(destaddr) assert addr_valid, "Invalid destination address: " + destaddr + \ ", error message: " + errormsg #TODO paramatetrize this as a test variable chooseOrdersFunc = weighted_order_choose log.debug('starting sendpayment') jm_single().bc_interface.sync_wallet(wallet) #Trigger PING LAG sending artificially joinmarket.irc.PING_INTERVAL = 3 mcs = [IRCMessageChannel(c) for c in get_irc_mchannels()] mcc = MessageChannelCollection(mcs) #hack fix for #356 if multiple orders per counterparty #removed for now. #if amount==0: makercount=2 taker = sendpayment.SendPayment(mcc, wallet, destaddr, amount, makercount - 2, txfee, waittime, mixdepth, answeryes, chooseOrdersFunc) try: log.debug('starting message channels') mcc.run(failures=fails) finally: if any(yigen_procs): for ygp in yigen_procs: #NB *GENTLE* shutdown is essential for #test coverage reporting! ygp.send_signal(signal.SIGINT) ygp.wait() #wait for block generation time.sleep(5) if not donate: received = jm_single().bc_interface.get_received_by_addr( [destaddr], None)['data'][0]['balance'] if amount != 0: assert received == amount, "sendpayment failed - coins not arrived, " +\ "received: " + str(received) #TODO: how to check success for sweep case? else: assert received != 0
def test_tx_commitments_used(setup_podle, consume_tx, age_required, cmt_age): tries = jm_single().config.getint("POLICY", "taker_utxo_retries") #remember and reset at the end taker_utxo_age = jm_single().config.getint("POLICY", "taker_utxo_age") jm_single().config.set("POLICY", "taker_utxo_age", str(age_required)) #Don't want to wait too long, but must account for possible #throttling with !auth jm_single().maker_timeout_sec = 12 amount = 0 wallets = make_wallets(3, wallet_structures=[[1, 2, 1, 0, 0], [1, 2, 0, 0, 0], [2, 2, 1, 0, 0]], mean_amt=1) #the sendpayment bot uses the last wallet in the list wallet = wallets[2]['wallet'] #make_wallets calls grab_coins which mines 1 block per individual payout, #so the age of the coins depends on where they are in that list. The sendpayment #is the last wallet in the list, and we choose the non-tx utxos which are in #mixdepth 1 and 2 (2 and 1 utxos in each respectively). We filter for those #that have sufficient age, so to get 1 which is old enough, it will be the oldest, #which will have an age of 2 + 1 (the first utxo spent to that wallet). #So if we need an age of 6, we need to mine 3 more blocks. blocks_reqd = cmt_age - 3 jm_single().bc_interface.tick_forward_chain(blocks_reqd) yigen_procs = [] for i in range(2): ygp = local_command([python_cmd, yg_cmd,\ str(wallets[i]['seed'])], bg=True) time.sleep(2) #give it a chance yigen_procs.append(ygp) time.sleep(5) destaddr = btc.privkey_to_address(binascii.hexlify(os.urandom(32)), magicbyte=get_p2pk_vbyte()) addr_valid, errormsg = validate_address(destaddr) assert addr_valid, "Invalid destination address: " + destaddr + \ ", error message: " + errormsg log.debug('starting sendpayment') jm_single().bc_interface.sync_wallet(wallet) log.debug("Here is the whole wallet: \n" + str(wallet.unspent)) #Trigger PING LAG sending artificially joinmarket.irc.PING_INTERVAL = 3 mcs = [IRCMessageChannel(c) for c in get_irc_mchannels()] mcc = MessageChannelCollection(mcs) if consume_tx: #add all utxo in mixdepth 0 to 'used' list of commitments, utxos = wallet.get_utxos_by_mixdepth()[0] for u, addrval in utxos.iteritems(): priv = wallet.get_key_from_addr(addrval['address']) podle = btc.PoDLE(u, priv) for i in range(tries): #loop because we want to use up all retries of this utxo commitment = podle.generate_podle(i)['commit'] btc.update_commitments(commitment=commitment) #Now test a sendpayment from mixdepth 0 with all the depth 0 utxos #used up, so that the other utxos in the wallet get used. taker = sendpayment.SendPayment(mcc, wallet, destaddr, amount, 2, 5000, 3, 0, True, weighted_order_choose) try: log.debug('starting message channels') mcc.run() finally: if any(yigen_procs): for ygp in yigen_procs: #NB *GENTLE* shutdown is essential for #test coverage reporting! ygp.send_signal(signal.SIGINT) ygp.wait() #wait for block generation time.sleep(5) received = jm_single().bc_interface.get_received_by_addr( [destaddr], None)['data'][0]['balance'] jm_single().config.set("POLICY", "taker_utxo_age", str(taker_utxo_age)) if cmt_age < age_required: assert received == 0, "Coins arrived but shouldn't" else: assert received != 0, "sendpayment failed - coins not arrived, " +\ "received: " + str(received)
def test_blockr_sync(setup_blockr, net, seed, gaplimit, showprivkey, method): jm_single().config.set("BLOCKCHAIN", "network", net) wallet = Wallet(seed, max_mix_depth=5) jm_single().bc_interface.sync_wallet(wallet) # copy pasted from wallet-tool; some boiled down form of # this should really be in wallet.py in the joinmarket module. def cus_print(s): print s total_balance = 0 for m in range(wallet.max_mix_depth): cus_print("mixing depth %d m/0/%d/" % (m, m)) balance_depth = 0 for forchange in [0, 1]: cus_print(" " + ("external" if forchange == 0 else "internal") + " addresses m/0/%d/%d/" % (m, forchange)) for k in range(wallet.index[m][forchange] + gaplimit): addr = wallet.get_addr(m, forchange, k) balance = 0.0 for addrvalue in wallet.unspent.values(): if addr == addrvalue["address"]: balance += addrvalue["value"] balance_depth += balance used = "used" if k < wallet.index[m][forchange] else " new" if showprivkey: privkey = btc.wif_compressed_privkey(wallet.get_key(m, forchange, k), get_p2pk_vbyte()) else: privkey = "" if method == "displayall" or balance > 0 or (used == " new" and forchange == 0): cus_print( " m/0/%d/%d/%03d %-35s%s %.8f btc %s" % (m, forchange, k, addr, used, balance / 1e8, privkey) ) total_balance += balance_depth print ("for mixdepth=%d balance=%.8fbtc" % (m, balance_depth / 1e8)) assert total_balance == 96085297
def test_sendpayment(setup_regtest, num_ygs, wallet_structures, mean_amt, mixdepth, sending_amt, ygcfs, fails, donate, rpcwallet): """Test of sendpayment code, with yield generators in background. """ log = get_log() makercount = num_ygs answeryes = True txfee = 5000 waittime = 5 amount = sending_amt wallets = make_wallets(makercount + 1, wallet_structures=wallet_structures, mean_amt=mean_amt) #the sendpayment bot uses the last wallet in the list if not rpcwallet: wallet = wallets[makercount]['wallet'] else: wallet = BitcoinCoreWallet(fromaccount="") yigen_procs = [] if ygcfs: assert makercount == len(ygcfs) for i in range(makercount): if ygcfs: #back up default config, overwrite before start os.rename("joinmarket.cfg", "joinmarket.cfg.bak") shutil.copy2(ygcfs[i], "joinmarket.cfg") ygp = local_command([python_cmd, yg_cmd,\ str(wallets[i]['seed'])], bg=True) time.sleep(2) #give it a chance yigen_procs.append(ygp) if ygcfs: #Note: in case of using multiple configs, #the starting config is what is used by sendpayment os.rename("joinmarket.cfg.bak", "joinmarket.cfg") #A significant delay is needed to wait for the yield generators to sync time.sleep(20) if donate: destaddr = None else: destaddr = btc.privkey_to_address( os.urandom(32), from_hex=False, magicbyte=get_p2pk_vbyte()) addr_valid, errormsg = validate_address(destaddr) assert addr_valid, "Invalid destination address: " + destaddr + \ ", error message: " + errormsg #TODO paramatetrize this as a test variable chooseOrdersFunc = weighted_order_choose log.debug('starting sendpayment') sync_wallet(wallet) #Trigger PING LAG sending artificially joinmarket.irc.PING_INTERVAL = 3 mcs = [IRCMessageChannel(c) for c in get_irc_mchannels()] mcc = MessageChannelCollection(mcs) #hack fix for #356 if multiple orders per counterparty #removed for now. #if amount==0: makercount=2 taker = sendpayment.SendPayment(mcc, wallet, destaddr, amount, makercount-2, txfee, waittime, mixdepth, answeryes, chooseOrdersFunc) try: log.debug('starting message channels') mcc.run(failures=fails) finally: if any(yigen_procs): for ygp in yigen_procs: #NB *GENTLE* shutdown is essential for #test coverage reporting! ygp.send_signal(signal.SIGINT) ygp.wait() #wait for block generation time.sleep(5) if not donate: received = jm_single().bc_interface.get_received_by_addr( [destaddr], None)['data'][0]['balance'] if amount != 0: assert received == amount, "sendpayment failed - coins not arrived, " +\ "received: " + str(received) #TODO: how to check success for sweep case? else: assert received != 0
def test_failed_sendpayment(setup_podle, num_ygs, wallet_structures, mean_amt, mixdepth, sending_amt): """Test of initiating joins, but failing to complete, to see commitment usage. YGs in background as per test_regtest. Use sweeps to avoid recover_from_nonrespondants without intruding into sendpayment code. """ makercount = num_ygs answeryes = True txfee = 5000 waittime = 3 #Don't want to wait too long, but must account for possible #throttling with !auth jm_single().maker_timeout_sec = 12 amount = 0 wallets = make_wallets(makercount + 1, wallet_structures=wallet_structures, mean_amt=mean_amt) #the sendpayment bot uses the last wallet in the list wallet = wallets[makercount]['wallet'] yigen_procs = [] for i in range(makercount): ygp = local_command([python_cmd, yg_cmd,\ str(wallets[i]['seed'])], bg=True) time.sleep(2) #give it a chance yigen_procs.append(ygp) #A significant delay is needed to wait for the yield generators to sync time.sleep(20) destaddr = btc.privkey_to_address( os.urandom(32), from_hex=False, magicbyte=get_p2pk_vbyte()) addr_valid, errormsg = validate_address(destaddr) assert addr_valid, "Invalid destination address: " + destaddr + \ ", error message: " + errormsg #TODO paramatetrize this as a test variable chooseOrdersFunc = weighted_order_choose log.debug('starting sendpayment') jm_single().bc_interface.sync_wallet(wallet) #Trigger PING LAG sending artificially joinmarket.irc.PING_INTERVAL = 3 mcs = [IRCMessageChannel(c) for c in get_irc_mchannels()] mcc = MessageChannelCollection(mcs) #Allow taker more retries than makers allow, so as to trigger #blacklist failure case jm_single().config.set("POLICY", "taker_utxo_retries", "4") #override ioauth receipt with a dummy do-nothing callback: def on_ioauth(*args): log.debug("Taker received: " + ','.join([str(x) for x in args])) class DummySendPayment(sendpayment.SendPayment): def __init__(self, msgchan, wallet, destaddr, amount, makercount, txfee, waittime, mixdepth, answeryes, chooseOrdersFunc, on_ioauth): self.on_ioauth = on_ioauth self.podle_fails = 0 self.podle_allowed_fails = 3 #arbitrary; but do it more than once self.retries = 0 super(DummySendPayment, self).__init__(msgchan, wallet, destaddr, amount, makercount, txfee, waittime, mixdepth, answeryes, chooseOrdersFunc) def on_welcome(self): Taker.on_welcome(self) DummyPaymentThread(self).start() class DummyPaymentThread(sendpayment.PaymentThread): def finishcallback(self, coinjointx): #Don't ignore makers and just re-start self.taker.retries += 1 if self.taker.podle_fails == self.taker.podle_allowed_fails: self.taker.msgchan.shutdown() return self.create_tx() def create_tx(self): try: super(DummyPaymentThread, self).create_tx() except btc.PoDLEError: log.debug("Got one commit failure, continuing") self.taker.podle_fails += 1 taker = DummySendPayment(mcc, wallet, destaddr, amount, makercount, txfee, waittime, mixdepth, answeryes, chooseOrdersFunc, on_ioauth) try: log.debug('starting message channels') mcc.run() finally: if any(yigen_procs): for ygp in yigen_procs: #NB *GENTLE* shutdown is essential for #test coverage reporting! ygp.send_signal(signal.SIGINT) ygp.wait() #We should have been able to try (tur -1) + podle_allowed_fails times assert taker.retries == jm_single().config.getint( "POLICY", "taker_utxo_retries") + taker.podle_allowed_fails #wait for block generation time.sleep(2) received = jm_single().bc_interface.get_received_by_addr( [destaddr], None)['data'][0]['balance'] #Sanity check no transaction succeeded assert received == 0
def test_external_commitment_used(setup_podle): tries = jm_single().config.getint("POLICY","taker_utxo_retries") #Don't want to wait too long, but must account for possible #throttling with !auth jm_single().maker_timeout_sec = 12 amount = 50000000 wallets = make_wallets(3, wallet_structures=[[1,0,0,0,0],[1,0,0,0,0],[1,1,0,0,0]], mean_amt=1) #the sendpayment bot uses the last wallet in the list wallet = wallets[2]['wallet'] yigen_procs = [] for i in range(2): ygp = local_command([python_cmd, yg_cmd,\ str(wallets[i]['seed'])], bg=True) time.sleep(2) #give it a chance yigen_procs.append(ygp) #A significant delay is needed to wait for the yield generators to sync time.sleep(10) destaddr = btc.privkey_to_address( binascii.hexlify(os.urandom(32)), magicbyte=get_p2pk_vbyte()) addr_valid, errormsg = validate_address(destaddr) assert addr_valid, "Invalid destination address: " + destaddr + \ ", error message: " + errormsg log.debug('starting sendpayment') jm_single().bc_interface.sync_wallet(wallet) #Trigger PING LAG sending artificially joinmarket.irc.PING_INTERVAL = 3 mcs = [IRCMessageChannel(c) for c in get_irc_mchannels()] mcc = MessageChannelCollection(mcs) #add all utxo in mixdepth 0 to 'used' list of commitments, utxos = wallet.get_utxos_by_mixdepth()[0] for u, addrval in utxos.iteritems(): priv = wallet.get_key_from_addr(addrval['address']) podle = btc.PoDLE(u, priv) for i in range(tries): #loop because we want to use up all retries of this utxo commitment = podle.generate_podle(i)['commit'] btc.update_commitments(commitment=commitment) #create a new utxo, notionally from an external source; to make life a little #easier we'll pay to another mixdepth, but this is OK because #taker does not source from here currently, only from the utxos chosen #for the transaction, not the whole wallet. So we can treat it as if #external (don't access its privkey). utxos = wallet.get_utxos_by_mixdepth()[1] ecs = {} for u, addrval in utxos.iteritems(): priv = wallet.get_key_from_addr(addrval['address']) ecs[u] = {} ecs[u]['reveal']={} for j in range(tries): P, P2, s, e, commit = generate_single_podle_sig( binascii.unhexlify(priv), j) if 'P' not in ecs[u]: ecs[u]['P'] = P ecs[u]['reveal'][j] = {'P2':P2, 's':s, 'e':e} btc.update_commitments(external_to_add=ecs) #Now the conditions described above hold. We do a normal single #sendpayment. taker = sendpayment.SendPayment(mcc, wallet, destaddr, amount, 2, 5000, 3, 0, True, weighted_order_choose) try: log.debug('starting message channels') mcc.run() finally: if any(yigen_procs): for ygp in yigen_procs: #NB *GENTLE* shutdown is essential for #test coverage reporting! ygp.send_signal(signal.SIGINT) ygp.wait() #wait for block generation time.sleep(5) received = jm_single().bc_interface.get_received_by_addr( [destaddr], None)['data'][0]['balance'] assert received == amount, "sendpayment failed - coins not arrived, " +\ "received: " + str(received) #Cleanup - remove the external commitments added btc.update_commitments(external_to_remove=ecs)
key = wallet.get_key_from_addr(av['address']) tries = btc.podle.get_podle_tries(u, key, max_tries) tries_remaining = max(0, max_tries - tries) unsp[u] = { 'mixdepth': mixdepth, 'address': av['address'], 'value': av['value'], 'tries': tries, 'tries_remaining': tries_remaining, 'external': False } if options.showprivkey: wifkey = btc.wif_compressed_privkey(key, vbyte=get_p2pk_vbyte()) unsp[u]['privkey'] = wifkey used_commitments, external_commitments = btc.podle.get_podle_commitments() for u, ec in external_commitments.iteritems(): tries = btc.podle.get_podle_tries(utxo=u, max_tries=max_tries, external=True) tries_remaining = max(0, max_tries - tries) unsp[u] = { 'tries': tries, 'tries_remaining': tries_remaining, 'external': True } print(json.dumps(unsp, indent=4))
def test_tx_commitments_used(setup_podle, consume_tx, age_required, cmt_age): tries = jm_single().config.getint("POLICY","taker_utxo_retries") #remember and reset at the end taker_utxo_age = jm_single().config.getint("POLICY", "taker_utxo_age") jm_single().config.set("POLICY", "taker_utxo_age", str(age_required)) #Don't want to wait too long, but must account for possible #throttling with !auth jm_single().maker_timeout_sec = 12 amount = 0 wallets = make_wallets(3, wallet_structures=[[1,2,1,0,0],[1,2,0,0,0],[2,2,1,0,0]], mean_amt=1) #the sendpayment bot uses the last wallet in the list wallet = wallets[2]['wallet'] #make_wallets calls grab_coins which mines 1 block per individual payout, #so the age of the coins depends on where they are in that list. The sendpayment #is the last wallet in the list, and we choose the non-tx utxos which are in #mixdepth 1 and 2 (2 and 1 utxos in each respectively). We filter for those #that have sufficient age, so to get 1 which is old enough, it will be the oldest, #which will have an age of 2 + 1 (the first utxo spent to that wallet). #So if we need an age of 6, we need to mine 3 more blocks. blocks_reqd = cmt_age - 3 jm_single().bc_interface.tick_forward_chain(blocks_reqd) yigen_procs = [] for i in range(2): ygp = local_command([python_cmd, yg_cmd,\ str(wallets[i]['seed'])], bg=True) time.sleep(2) #give it a chance yigen_procs.append(ygp) time.sleep(5) destaddr = btc.privkey_to_address( binascii.hexlify(os.urandom(32)), magicbyte=get_p2pk_vbyte()) addr_valid, errormsg = validate_address(destaddr) assert addr_valid, "Invalid destination address: " + destaddr + \ ", error message: " + errormsg log.debug('starting sendpayment') jm_single().bc_interface.sync_wallet(wallet) log.debug("Here is the whole wallet: \n" + str(wallet.unspent)) #Trigger PING LAG sending artificially joinmarket.irc.PING_INTERVAL = 3 mcs = [IRCMessageChannel(c) for c in get_irc_mchannels()] mcc = MessageChannelCollection(mcs) if consume_tx: #add all utxo in mixdepth 0 to 'used' list of commitments, utxos = wallet.get_utxos_by_mixdepth()[0] for u, addrval in utxos.iteritems(): priv = wallet.get_key_from_addr(addrval['address']) podle = btc.PoDLE(u, priv) for i in range(tries): #loop because we want to use up all retries of this utxo commitment = podle.generate_podle(i)['commit'] btc.update_commitments(commitment=commitment) #Now test a sendpayment from mixdepth 0 with all the depth 0 utxos #used up, so that the other utxos in the wallet get used. taker = sendpayment.SendPayment(mcc, wallet, destaddr, amount, 2, 5000, 3, 0, True, weighted_order_choose) try: log.debug('starting message channels') mcc.run() finally: if any(yigen_procs): for ygp in yigen_procs: #NB *GENTLE* shutdown is essential for #test coverage reporting! ygp.send_signal(signal.SIGINT) ygp.wait() #wait for block generation time.sleep(5) received = jm_single().bc_interface.get_received_by_addr( [destaddr], None)['data'][0]['balance'] jm_single().config.set("POLICY", "taker_utxo_age", str(taker_utxo_age)) if cmt_age < age_required: assert received == 0, "Coins arrived but shouldn't" else: assert received != 0, "sendpayment failed - coins not arrived, " +\ "received: " + str(received)
def 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
xpub_key = '' cus_print(' ' + ('external' if forchange == 0 else 'internal') + ' addresses m/0/%d/%d' % (m, forchange) + ' ' + xpub_key) for k in range(wallet.index[m][forchange] + options.gaplimit): addr = wallet.get_addr(m, forchange, k) balance = 0.0 for addrvalue in wallet.unspent.values(): if addr == addrvalue['address']: balance += addrvalue['value'] balance_depth += balance used = ('used' if k < wallet.index[m][forchange] else ' new') if options.showprivkey: if btc.secp_present: privkey = btc.wif_compressed_privkey( wallet.get_key(m, forchange, k), get_p2pk_vbyte()) else: privkey = btc.encode_privkey(wallet.get_key(m, forchange, k), 'wif_compressed', get_p2pk_vbyte()) else: privkey = '' if (method == 'displayall' or balance > 0 or (used == ' new' and forchange == 0)): cus_print(' m/0/%d/%d/%03d %-35s%s %.8f btc %s' % (m, forchange, k, addr, used, balance / 1e8, privkey)) if m in wallet.imported_privkeys: cus_print(' import addresses') for privkey in wallet.imported_privkeys[m]: addr = btc.privtoaddr(privkey, magicbyte=get_p2pk_vbyte()) balance = 0.0
for m in range(wallet.max_mix_depth): cus_print("mixing depth %d m/0/%d/" % (m, m)) balance_depth = 0 for forchange in [0, 1]: cus_print(" " + ("external" if forchange == 0 else "internal") + " addresses m/0/%d/%d/" % (m, forchange)) for k in range(wallet.index[m][forchange] + options.gaplimit): addr = wallet.get_addr(m, forchange, k) balance = 0.0 for addrvalue in wallet.unspent.values(): if addr == addrvalue["address"]: balance += addrvalue["value"] balance_depth += balance used = "used" if k < wallet.index[m][forchange] else " new" privkey = ( btc.encode_privkey(wallet.get_key(m, forchange, k), "wif_compressed", get_p2pk_vbyte()) if options.showprivkey else "" ) if method == "displayall" or balance > 0 or (used == " new" and forchange == 0): cus_print( " m/0/%d/%d/%03d %-35s%s %.8f btc %s" % (m, forchange, k, addr, used, balance / 1e8, privkey) ) if m in wallet.imported_privkeys: cus_print(" import addresses") for privkey in wallet.imported_privkeys[m]: addr = btc.privtoaddr(privkey, get_p2pk_vbyte()) balance = 0.0 for addrvalue in wallet.unspent.values(): if addr == addrvalue["address"]: balance += addrvalue["value"]
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
def test_external_commitment_used(setup_podle): tries = jm_single().config.getint("POLICY", "taker_utxo_retries") #Don't want to wait too long, but must account for possible #throttling with !auth jm_single().maker_timeout_sec = 12 amount = 50000000 wallets = make_wallets(3, wallet_structures=[[1, 0, 0, 0, 0], [1, 0, 0, 0, 0], [1, 1, 0, 0, 0]], mean_amt=1) #the sendpayment bot uses the last wallet in the list wallet = wallets[2]['wallet'] yigen_procs = [] for i in range(2): ygp = local_command([python_cmd, yg_cmd,\ str(wallets[i]['seed'])], bg=True) time.sleep(2) #give it a chance yigen_procs.append(ygp) #A significant delay is needed to wait for the yield generators to sync time.sleep(10) destaddr = btc.privkey_to_address(binascii.hexlify(os.urandom(32)), magicbyte=get_p2pk_vbyte()) addr_valid, errormsg = validate_address(destaddr) assert addr_valid, "Invalid destination address: " + destaddr + \ ", error message: " + errormsg log.debug('starting sendpayment') jm_single().bc_interface.sync_wallet(wallet) #Trigger PING LAG sending artificially joinmarket.irc.PING_INTERVAL = 3 mcs = [IRCMessageChannel(c) for c in get_irc_mchannels()] mcc = MessageChannelCollection(mcs) #add all utxo in mixdepth 0 to 'used' list of commitments, utxos = wallet.get_utxos_by_mixdepth()[0] for u, addrval in utxos.iteritems(): priv = wallet.get_key_from_addr(addrval['address']) podle = btc.PoDLE(u, priv) for i in range(tries): #loop because we want to use up all retries of this utxo commitment = podle.generate_podle(i)['commit'] btc.update_commitments(commitment=commitment) #create a new utxo, notionally from an external source; to make life a little #easier we'll pay to another mixdepth, but this is OK because #taker does not source from here currently, only from the utxos chosen #for the transaction, not the whole wallet. So we can treat it as if #external (don't access its privkey). utxos = wallet.get_utxos_by_mixdepth()[1] ecs = {} for u, addrval in utxos.iteritems(): priv = wallet.get_key_from_addr(addrval['address']) ecs[u] = {} ecs[u]['reveal'] = {} for j in range(tries): P, P2, s, e, commit = generate_single_podle_sig( binascii.unhexlify(priv), j) if 'P' not in ecs[u]: ecs[u]['P'] = P ecs[u]['reveal'][j] = {'P2': P2, 's': s, 'e': e} btc.update_commitments(external_to_add=ecs) #Now the conditions described above hold. We do a normal single #sendpayment. taker = sendpayment.SendPayment(mcc, wallet, destaddr, amount, 2, 5000, 3, 0, True, weighted_order_choose) try: log.debug('starting message channels') mcc.run() finally: if any(yigen_procs): for ygp in yigen_procs: #NB *GENTLE* shutdown is essential for #test coverage reporting! ygp.send_signal(signal.SIGINT) ygp.wait() #wait for block generation time.sleep(5) received = jm_single().bc_interface.get_received_by_addr( [destaddr], None)['data'][0]['balance'] assert received == amount, "sendpayment failed - coins not arrived, " +\ "received: " + str(received) #Cleanup - remove the external commitments added btc.update_commitments(external_to_remove=ecs)
def test_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