def setUp(self): self.n = 2 #create n new random wallets. #put coins into the first mixdepth for each #The amount is 1btc + 300,000 satoshis, to account #for a 0.2% fee for 1 counterparty + a large tx fee. #(but not large enough to handle the bad wallet) wallet_structures = [[1, 0, 0, 0, 0]] * (self.n) self.wallets = make_wallets(self.n, wallet_structures=wallet_structures, mean_amt=1.00300000) #the sender is wallet (n), i.e. index wallets[n-1] #we need a counterparty with a huge set of utxos. bad_wallet_struct = [[1, 0, 0, 0, 0]] self.wallets.update( make_wallets(1, wallet_structures=bad_wallet_struct, mean_amt=0.01, start_index=2)) #having created the bad wallet, add lots of utxos to #the same mixdepth print 'creating a crazy amount of utxos in one wallet...' r_addr = self.wallets[2]['wallet'].get_external_addr(0) for i in range(60): jm_single().bc_interface.grab_coins(r_addr, 0.02) time.sleep(1) #for sweep, create a yg wallet with enough for the mix #of the bad wallet above (acting as sender) self.wallets.update( make_wallets(1, wallet_structures=[[1, 0, 0, 0, 0]], mean_amt=3, start_index=3))
def make_tx_add_notify(): wallet_dict = make_wallets(1, [[1, 0, 0, 0, 0]], mean_amt=4, sdev_amt=0)[0] amount = 250000000 txfee = 10000 wallet = wallet_dict['wallet'] sync_wallet(wallet) inputs = wallet.select_utxos(0, amount) ins = inputs.keys() input_value = sum([i['value'] for i in inputs.values()]) output_addr = wallet.get_new_addr(1, 0) change_addr = wallet.get_new_addr(0, 1) outs = [{ 'value': amount, 'address': output_addr }, { 'value': input_value - amount - txfee, 'address': change_addr }] tx = btc.mktx(ins, outs) de_tx = btc.deserialize(tx) for index, ins in enumerate(de_tx['ins']): utxo = ins['outpoint']['hash'] + ':' + str(ins['outpoint']['index']) addr = inputs[utxo]['address'] priv = wallet.get_key_from_addr(addr) tx = btc.sign(tx, index, priv) unconfirm_called[0] = confirm_called[0] = False timeout_unconfirm_called[0] = timeout_confirm_called[0] = False jm_single().bc_interface.add_tx_notify(btc.deserialize(tx), unconfirm_callback, confirm_callback, output_addr, timeout_callback) return tx
def test_wallet_sync_from_scratch(setup_wallets, wallet_structure, wallet_file, password, ic): """Simulate a scenario in which we use a new bitcoind, thusly: generate a new wallet and simply pretend that it has an existing index_cache. This will force import of all addresses up to the index_cache values. """ setup_import(mainnet=False) wallet = make_wallets(1,[wallet_structure], fixed_seeds=[wallet_file], test_wallet=True, passwords=[password])[0]['wallet'] sync_count = 0 jm_single().bc_interface.wallet_synced = False wallet.index_cache = ic while not jm_single().bc_interface.wallet_synced: wallet.index = [] for i in range(5): wallet.index.append([0, 0]) jm_single().bc_interface.sync_wallet(wallet) sync_count += 1 #avoid infinite loop assert sync_count < 10 log.debug("Tried " + str(sync_count) + " times") #after #586 we expect to ALWAYS succeed within 2 rounds assert sync_count == 2 #for each external branch, the new index may be higher than #the original index_cache if there was a higher used address expected_wallet_index = [] for i, val in enumerate(wallet_structure): if val > wallet.index_cache[i][0]: expected_wallet_index.append([val, wallet.index_cache[i][1]]) else: expected_wallet_index.append([wallet.index_cache[i][0], wallet.index_cache[i][1]]) assert wallet.index == expected_wallet_index
def test_broadcast_self(setup_tx_notify): cjtx = create_testing_cjtx(['counterparty']) jm_single().config.set('POLICY', 'tx_broadcast', 'self') cjtx.push() assert self_pushtx_count[0] == 1 assert sum(msgchan_pushtx_count[0].values()) == 0 return True
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(): 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 test_start_ygs(setup_ygrunner, num_ygs, wallet_structures, mean_amt): """Set up some wallets, for the ygs and 1 sp. Then start the ygs in background and publish the seed of the sp wallet for easy import into -qt """ wallets = make_wallets(num_ygs + 1, wallet_structures=wallet_structures, mean_amt=mean_amt) #the sendpayment bot uses the last wallet in the list wallet = wallets[num_ygs]['wallet'] print "Seed : " + wallets[num_ygs]['seed'] #useful to see the utxos on screen sometimes jm_single().bc_interface.sync_wallet(wallet) print wallet.unspent 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) try: while True: time.sleep(20) print 'waiting' 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()
def init_tx(self, tx, balance, sweep): destaddr = None if tx['destination'] == 'internal': destaddr = self.taker.wallet.get_internal_addr(tx['srcmixdepth'] + 1) elif tx['destination'] == 'addrask': jm_single().debug_silence[0] = True while True: destaddr = raw_input('insert new address: ') addr_valid, errormsg = validate_address(destaddr) if addr_valid: break print('Address ' + destaddr + ' invalid. ' + errormsg + ' try again') jm_single().debug_silence[0] = False else: destaddr = tx['destination'] self.taker.wallet.update_cache_index() self.sweep = sweep self.balance = balance self.tx = tx self.destaddr = destaddr self.create_tx_attempts = self.taker.options.maxcreatetx self.create_tx() with self.lockcond: self.lockcond.wait() log.debug('tx confirmed, waiting for ' + str(tx['wait']) + ' minutes') time.sleep(tx['wait'] * 60) log.debug('woken')
def setUp(self): self.n = 2 #create n new random wallets. #put coins into the first mixdepth for each #The amount is 1btc + 300,000 satoshis, to account #for a 0.2% fee for 1 counterparty + a large tx fee. #(but not large enough to handle the bad wallet) wallet_structures = [[1, 0, 0, 0, 0]] * (self.n) self.wallets = make_wallets(self.n, wallet_structures=wallet_structures, mean_amt=1.00300000) #the sender is wallet (n), i.e. index wallets[n-1] #we need a counterparty with a huge set of utxos. bad_wallet_struct = [[1, 0, 0, 0, 0]] self.wallets.update(make_wallets(1, wallet_structures=bad_wallet_struct, mean_amt=0.01, start_index=2)) #having created the bad wallet, add lots of utxos to #the same mixdepth print 'creating a crazy amount of utxos in one wallet...' r_addr = self.wallets[2]['wallet'].get_external_addr(0) for i in range(60): jm_single().bc_interface.grab_coins(r_addr, 0.02) time.sleep(1) #for sweep, create a yg wallet with enough for the mix #of the bad wallet above (acting as sender) self.wallets.update(make_wallets(1, wallet_structures=[[1, 0, 0, 0, 0]], mean_amt=3, start_index=3))
def test_utxo_selection(setup_wallets, nw, wallet_structures, mean_amt, sdev_amt, amount): """Check that all the utxo selection algorithms work with a random variety of wallet contents. """ wallets = make_wallets(nw, wallet_structures, mean_amt, sdev_amt) for w in wallets.values(): jm_single().bc_interface.wallet_synced = False jm_single().bc_interface.sync_wallet(w['wallet']) for k, w in enumerate(wallets.values()): for algo in [select_gradual, select_greedy, select_greediest, None]: wallet = w['wallet'] if algo: wallet.utxo_selector = algo if k == 0: with pytest.raises(Exception) as e_info: selected = wallet.select_utxos(1, amount) else: selected = wallet.select_utxos(1, amount) algostr = algo.__name__ if algo else "default" print 'selected these for algo ' + algostr + ':' print selected #basic check: #does this algo actually generate sufficient coins? total_selected = sum([x['value'] for x in selected.values()]) assert total_selected > amount, "Selection algo: " + algo + \ "failed to select sufficient coins, total: " + \ str(total_selected) + ", should be: " + str(amount)
def init_tx(self, tx, balance, sweep): destaddr = None if tx['destination'] == 'internal': destaddr = self.taker.wallet.get_internal_addr(tx['srcmixdepth'] + 1) elif tx['destination'] == 'addrask': jm_single().debug_silence[0] = True print('\n'.join(['=' * 60] * 3)) print('Tumbler requires more addresses to stop amount correlation') print('Obtain a new destination address from your bitcoin recipient') print(' for example click the button that gives a new deposit address') print('\n'.join(['=' * 60] * 1)) while True: destaddr = raw_input('insert new address: ') addr_valid, errormsg = validate_address(destaddr) if addr_valid: break print( 'Address ' + destaddr + ' invalid. ' + errormsg + ' try again') jm_single().debug_silence[0] = False else: destaddr = tx['destination'] self.taker.wallet.update_cache_index() self.sweep = sweep self.balance = balance self.tx = tx self.destaddr = destaddr self.create_tx_attempts = self.taker.options['maxcreatetx'] self.create_tx() with self.lockcond: self.lockcond.wait() log.debug('tx confirmed, waiting for ' + str(tx['wait']) + ' minutes') time.sleep(tx['wait'] * 60) log.debug('woken')
def make_wallets(n, wallet_structures=None, mean_amt=1, sdev_amt=0, start_index=0): '''n: number of wallets to be created wallet_structure: array of n arrays , each subarray specifying the number of addresses to be populated with coins at each depth (for now, this will only populate coins into 'receive' addresses) mean_amt: the number of coins (in btc units) in each address as above sdev_amt: if randomness in amouts is desired, specify here. Returns: a dict of dicts of form {0:{'seed':seed,'wallet':Wallet object},1:..,}''' if len(wallet_structures) != n: raise Exception("Number of wallets doesn't match wallet structures") seeds = chunks(binascii.hexlify(os.urandom(15 * n)), n) wallets = {} for i in range(n): wallets[i + start_index] = { 'seed': seeds[i], 'wallet': Wallet(seeds[i], max_mix_depth=5) } for j in range(5): for k in range(wallet_structures[i][j]): deviation = sdev_amt * random.random() amt = mean_amt - sdev_amt / 2.0 + deviation if amt < 0: amt = 0.001 jm_single().bc_interface.grab_coins( wallets[i + start_index]['wallet'].get_external_addr(j), amt) return wallets
def oid_to_order(self, cjorder, oid, amount): total_amount = amount + cjorder.txfee mix_balance = self.wallet.get_balance_by_mixdepth() max_mix = max(mix_balance, key=mix_balance.get) filtered_mix_balance = [m for m in mix_balance.iteritems() if m[1] >= total_amount] log.debug('mix depths that have enough = ' + str(filtered_mix_balance)) filtered_mix_balance = sorted(filtered_mix_balance, key=lambda x: x[0]) mixdepth = filtered_mix_balance[0][0] log.debug('filling offer, mixdepth=' + str(mixdepth)) # mixdepth is the chosen depth we'll be spending from cj_addr = self.wallet.get_internal_addr((mixdepth + 1) % self.wallet.max_mix_depth) change_addr = self.wallet.get_internal_addr(mixdepth) utxos = self.wallet.select_utxos(mixdepth, total_amount) my_total_in = sum([va['value'] for va in utxos.values()]) real_cjfee = calc_cj_fee(cjorder.ordertype, cjorder.cjfee, amount) change_value = my_total_in - amount - cjorder.txfee + real_cjfee if change_value <= jm_single().DUST_THRESHOLD: log.debug(('change value={} below dust threshold, ' 'finding new utxos').format(change_value)) try: utxos = self.wallet.select_utxos( mixdepth, total_amount + jm_single().DUST_THRESHOLD) except Exception: log.debug('dont have the required UTXOs to make a ' 'output above the dust threshold, quitting') return None, None, None return utxos, cj_addr, change_addr
def setup_import(mainnet=True): try: os.remove("wallets/test_import_wallet.json") except: pass if mainnet: jm_single().config.set("BLOCKCHAIN", "network", "mainnet") pwd = 'import-pwd' test_in = [pwd, pwd, 'test_import_wallet.json'] expected = [ 'Enter wallet encryption passphrase:', 'Reenter wallet encryption passphrase:', 'Input wallet file name' ] testlog = open('test/testlog-' + pwd, 'wb') p = pexpect.spawn('python wallet-tool.py generate', logfile=testlog) interact(p, test_in, expected) p.expect('saved to') time.sleep(1) p.close() testlog.close() #anything to check in the log? with open(os.path.join('test', 'testlog-' + pwd)) as f: print f.read() if p.exitstatus != 0: raise Exception('failed due to exit status: ' + str(p.exitstatus)) jm_single().config.set("BLOCKCHAIN", "network", "testnet")
def test_create_sighash_txs(setup_tx_creation): #non-standard hash codes: for sighash in [ btc.SIGHASH_ANYONECANPAY + btc.SIGHASH_SINGLE, btc.SIGHASH_NONE, btc.SIGHASH_SINGLE ]: wallet = make_wallets(1, [[2, 0, 0, 0, 1]], 3)[0]['wallet'] jm_single().bc_interface.sync_wallet(wallet) amount = 350000000 ins_full = wallet.select_utxos(0, amount) print "using hashcode: " + str(sighash) txid = make_sign_and_push(ins_full, wallet, amount, hashcode=sighash) assert txid #Create an invalid sighash single (too many inputs) extra = wallet.select_utxos(4, 100000000) #just a few more inputs ins_full.update(extra) with pytest.raises(Exception) as e_info: txid = make_sign_and_push(ins_full, wallet, amount, hashcode=btc.SIGHASH_SINGLE) #trigger insufficient funds with pytest.raises(Exception) as e_info: fake_utxos = wallet.select_utxos(4, 1000000000)
def test_spend_p2sh_utxos(setup_tx_creation): #make a multisig address from 3 privs privs = [chr(x) * 32 + '\x01' for x in range(1, 4)] pubs = [btc.privkey_to_pubkey(binascii.hexlify(priv)) for priv in privs] script = btc.mk_multisig_script(pubs, 2) msig_addr = btc.scriptaddr(script, magicbyte=196) #pay into it wallet = make_wallets(1, [[2, 0, 0, 0, 1]], 3)[0]['wallet'] jm_single().bc_interface.sync_wallet(wallet) amount = 350000000 ins_full = wallet.select_utxos(0, amount) txid = make_sign_and_push(ins_full, wallet, amount, output_addr=msig_addr) assert txid #wait for mining time.sleep(4) #spend out; the input can be constructed from the txid of previous msig_in = txid + ":0" ins = [msig_in] #random output address and change addr output_addr = wallet.get_new_addr(1, 1) amount2 = amount - 50000 outs = [{'value': amount2, 'address': output_addr}] tx = btc.mktx(ins, outs) sigs = [] for priv in privs[:2]: sigs.append(btc.multisign(tx, 0, script, binascii.hexlify(priv))) tx = btc.apply_multisignatures(tx, 0, script, sigs) txid = jm_single().bc_interface.pushtx(tx) assert txid
def make_tx_add_notify(): wallet_dict = make_wallets(1, [[1, 0, 0, 0, 0]], mean_amt=4, sdev_amt=0)[0] amount = 250000000 txfee = 10000 wallet = wallet_dict['wallet'] sync_wallet(wallet) inputs = wallet.select_utxos(0, amount) ins = inputs.keys() input_value = sum([i['value'] for i in inputs.values()]) output_addr = wallet.get_new_addr(1, 0) change_addr = wallet.get_new_addr(0, 1) outs = [{'value': amount, 'address': output_addr}, {'value': input_value - amount - txfee, 'address': change_addr}] tx = btc.mktx(ins, outs) de_tx = btc.deserialize(tx) for index, ins in enumerate(de_tx['ins']): utxo = ins['outpoint']['hash'] + ':' + str(ins['outpoint']['index']) addr = inputs[utxo]['address'] priv = wallet.get_key_from_addr(addr) tx = btc.sign(tx, index, priv) unconfirm_called[0] = confirm_called[0] = False timeout_unconfirm_called[0] = timeout_confirm_called[0] = False jm_single().bc_interface.add_tx_notify( btc.deserialize(tx), unconfirm_callback, confirm_callback, output_addr, timeout_callback) return tx
def test_wif_privkeys_valid(setup_keys): with open("test/base58_keys_valid.json", "r") as f: json_data = f.read() valid_keys_list = json.loads(json_data) for a in valid_keys_list: key, hex_key, prop_dict = a if prop_dict["isPrivkey"]: netval = "testnet" if prop_dict["isTestnet"] else "mainnet" jm_single().config.set("BLOCKCHAIN", "network", netval) print 'testing this key: ' + key assert chr(btc.get_version_byte( key)) in '\x80\xef', "not valid network byte" if "decode_privkey" in dir(btc): from_wif_key = btc.decode_privkey(key) expected_key = btc.decode_privkey(hex_key) else: comp = prop_dict["isCompressed"] from_wif_key = btc.from_wif_privkey( key, compressed=comp, vbyte=btc.get_version_byte(key)-128) expected_key = hex_key if comp: expected_key += '01' assert from_wif_key == expected_key, "Incorrect key decoding: " + \ str(from_wif_key) + ", should be: " + str(expected_key)
def setup_import(mainnet=True): try: os.remove("wallets/test_import_wallet.json") except: pass if mainnet: jm_single().config.set("BLOCKCHAIN", "network", "mainnet") pwd = 'import-pwd' test_in = [pwd, pwd, 'test_import_wallet.json'] expected = ['Enter wallet encryption passphrase:', 'Reenter wallet encryption passphrase:', 'Input wallet file name'] testlog = open('test/testlog-' + pwd, 'wb') p = pexpect.spawn('python wallet-tool.py generate', logfile=testlog) interact(p, test_in, expected) p.expect('saved to') time.sleep(1) p.close() testlog.close() #anything to check in the log? with open(os.path.join('test', 'testlog-' + pwd)) as f: print f.read() if p.exitstatus != 0: raise Exception('failed due to exit status: ' + str(p.exitstatus)) jm_single().config.set("BLOCKCHAIN", "network", "testnet")
def test_wif_privkeys_valid(setup_keys): with open("test/base58_keys_valid.json", "r") as f: json_data = f.read() valid_keys_list = json.loads(json_data) for a in valid_keys_list: key, hex_key, prop_dict = a if prop_dict["isPrivkey"]: netval = "testnet" if prop_dict["isTestnet"] else "mainnet" jm_single().config.set("BLOCKCHAIN", "network", netval) print 'testing this key: ' + key assert chr(btc.get_version_byte( key)) in '\x80\xef', "not valid network byte" if "decode_privkey" in dir(btc): from_wif_key = btc.decode_privkey(key) expected_key = btc.decode_privkey(hex_key) else: comp = prop_dict["isCompressed"] from_wif_key = btc.from_wif_privkey( key, compressed=comp, vbyte=btc.get_version_byte(key) - 128) expected_key = hex_key if comp: expected_key += '01' assert from_wif_key == expected_key, "Incorrect key decoding: " + \ str(from_wif_key) + ", should be: " + str(expected_key)
def oid_to_order(self, cjorder, oid, amount): total_amount = amount + cjorder.txfee mix_balance = self.wallet.get_balance_by_mixdepth() max_mix = max(mix_balance, key=mix_balance.get) filtered_mix_balance = [ m for m in mix_balance.iteritems() if m[1] >= total_amount ] log.debug('mix depths that have enough = ' + str(filtered_mix_balance)) filtered_mix_balance = sorted(filtered_mix_balance, key=lambda x: x[0]) mixdepth = filtered_mix_balance[0][0] log.debug('filling offer, mixdepth=' + str(mixdepth)) # mixdepth is the chosen depth we'll be spending from cj_addr = self.wallet.get_internal_addr( (mixdepth + 1) % self.wallet.max_mix_depth) change_addr = self.wallet.get_internal_addr(mixdepth) utxos = self.wallet.select_utxos(mixdepth, total_amount) my_total_in = sum([va['value'] for va in utxos.values()]) real_cjfee = calc_cj_fee(cjorder.ordertype, cjorder.cjfee, amount) change_value = my_total_in - amount - cjorder.txfee + real_cjfee if change_value <= jm_single().DUST_THRESHOLD: log.debug(('change value={} below dust threshold, ' 'finding new utxos').format(change_value)) try: utxos = self.wallet.select_utxos( mixdepth, total_amount + jm_single().DUST_THRESHOLD) except Exception: log.debug('dont have the required UTXOs to make a ' 'output above the dust threshold, quitting') return None, None, None return utxos, cj_addr, change_addr
def init_tx(self, tx, balance, sweep): destaddr = None if tx['destination'] == 'internal': destaddr = self.taker.wallet.get_internal_addr(tx['srcmixdepth'] + 1) elif tx['destination'] == 'addrask': jm_single().debug_silence = True while True: destaddr = raw_input('insert new address: ') addr_valid, errormsg = validate_address(destaddr) if addr_valid: break print( 'Address ' + destaddr + ' invalid. ' + errormsg + ' try again') jm_single().debug_silence = False else: destaddr = tx['destination'] self.sweep = sweep self.balance = balance self.tx = tx self.destaddr = destaddr self.create_tx() self.lockcond.acquire() self.lockcond.wait() self.lockcond.release() log.debug('tx confirmed, waiting for ' + str(tx['wait']) + ' minutes') time.sleep(tx['wait'] * 60) log.debug('woken')
def main(): parser = OptionParser( usage= 'usage: %prog [options] utxo destaddr1 destaddr2 ..', description="For creating multiple utxos from one (for commitments in JM)." "Provide a utxo in form txid:N that has some unspent coins;" "Specify a list of destination addresses and the coins will" "be split equally between them (after bitcoin fees)." "You'll be prompted to enter the private key for the utxo" "during the run; it must be in WIF compressed format." "After the transaction is completed, the utxo strings for" "the new outputs will be shown." "Note that these utxos will not be ready for use as external" "commitments in Joinmarket until 5 confirmations have passed." " 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( '-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() if len(args) < 2: quit(parser, 'Invalid syntax') 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) destaddrs = args[1:] for d in destaddrs: if not validate_address(d): quit(parser, "Address was not valid; wrong network?: " + d) txsigned = sign(u, priv, destaddrs) log.debug("Got signed transaction:\n" + txsigned) log.debug("Deserialized:") log.debug(pformat(btc.deserialize(txsigned))) if raw_input('Would you like to push to the network? (y/n):')[0] != 'y': log.debug("You chose not to broadcast the transaction, quitting.") return jm_single().bc_interface.pushtx(txsigned)
def setup_blockr(request): def blockr_teardown(): jm_single().config.set("BLOCKCHAIN", "blockchain_source", "regtest") jm_single().config.set("BLOCKCHAIN", "network", "testnet") request.addfinalizer(blockr_teardown) load_program_config() jm_single().config.set("BLOCKCHAIN", "blockchain_source", "blockr") jm_single().bc_interface = BlockrInterface(True)
def test_no_timeout(setup_tx_notify): txhex = make_tx_add_notify() jm_single().bc_interface.pushtx(txhex) time.sleep(6) assert unconfirm_called[0] assert confirm_called[0] assert not timeout_unconfirm_called[0] assert not timeout_confirm_called[0] return True
def test_broadcast_not_self(setup_tx_notify): cjtx = create_testing_cjtx(['one', 'two', 'three', 'four']) jm_single().config.set('POLICY', 'tx_broadcast', 'not-self') N = 1000 for i in xrange(N): cjtx.push() assert self_pushtx_count[0] == 0 assert all(a > 1 for a in msgchan_pushtx_count[0].values()) return True
def direct_send(wallet, amount, mixdepth, destaddr, answeryes=False): """Send coins directly from one mixdepth to one destination address; does not need IRC. Sweep as for normal sendpayment (set amount=0). """ #Sanity checks; note destaddr format is carefully checked in startup assert isinstance(mixdepth, int) assert mixdepth >= 0 assert isinstance(amount, int) assert amount >= 0 and amount < 10000000000 assert isinstance(wallet, Wallet) import bitcoin as btc from pprint import pformat if amount == 0: utxos = wallet.get_utxos_by_mixdepth()[mixdepth] if utxos == {}: log.error("There are no utxos in mixdepth: " + str(mixdepth) + ", quitting.") return total_inputs_val = sum([va['value'] for u, va in utxos.iteritems()]) fee_est = estimate_tx_fee(len(utxos), 1) outs = [{"address": destaddr, "value": total_inputs_val - fee_est}] else: initial_fee_est = estimate_tx_fee(8, 2) #8 inputs to be conservative utxos = wallet.select_utxos(mixdepth, amount + initial_fee_est) if len(utxos) < 8: fee_est = estimate_tx_fee(len(utxos), 2) else: fee_est = initial_fee_est total_inputs_val = sum([va['value'] for u, va in utxos.iteritems()]) changeval = total_inputs_val - fee_est - amount outs = [{"value": amount, "address": destaddr}] change_addr = wallet.get_internal_addr(mixdepth) outs.append({"value": changeval, "address": change_addr}) #Now ready to construct transaction log.info("Using a fee of : " + str(fee_est) + " satoshis.") if amount != 0: log.info("Using a change value of: " + str(changeval) + " satoshis.") tx = btc.mktx(utxos.keys(), outs) stx = btc.deserialize(tx) for index, ins in enumerate(stx['ins']): utxo = ins['outpoint']['hash'] + ':' + str(ins['outpoint']['index']) addr = utxos[utxo]['address'] tx = btc.sign(tx, index, wallet.get_key_from_addr(addr)) txsigned = btc.deserialize(tx) log.info("Got signed transaction:\n") log.info(tx + "\n") log.info(pformat(txsigned)) if not answeryes: if raw_input( 'Would you like to push to the network? (y/n):')[0] != 'y': log.info("You chose not to broadcast the transaction, quitting.") return jm_single().bc_interface.pushtx(tx) txid = btc.txhash(tx) log.info("Transaction sent: " + txid + ", shutting down")
def test_wif_privkeys_invalid(setup_keys): #first try to create wif privkey from key of wrong length bad_privs = ['\x01\x02' * 17] #some silly private key but > 33 bytes #next try to create wif with correct length but wrong compression byte bad_privs.append('\x07' * 32 + '\x02') for priv in bad_privs: with pytest.raises(Exception) as e_info: fake_wif = btc.wif_compressed_privkey(binascii.hexlify(priv)) #Create a wif with wrong length bad_wif1 = btc.bin_to_b58check('\x01\x02' * 34, 128) #Create a wif with wrong compression byte bad_wif2 = btc.bin_to_b58check('\x07' * 33, 128) for bw in [bad_wif1, bad_wif2]: with pytest.raises(Exception) as e_info: fake_priv = btc.from_wif_privkey(bw) #Some invalid b58 from bitcoin repo; #none of these are valid as any kind of key or address with open("test/base58_keys_invalid.json", "r") as f: json_data = f.read() invalid_key_list = json.loads(json_data) for k in invalid_key_list: bad_key = k[0] for netval in ["mainnet", "testnet"]: jm_single().config.set("BLOCKCHAIN", "network", netval) #if using py.test -s ; sanity check to see what's actually being tested print 'testing this key: ' + bad_key if "decode_privkey" in dir(btc): try: bad_key_format = btc.get_privkey_format(bad_key) print 'has correct format: ' + bad_key_format except: pass #should throw exception with pytest.raises(Exception) as e_info: if "decode_privkey" in dir(btc): from_wif_key = btc.decode_privkey(bad_key) else: from_wif_key = btc.from_wif_compressed_privkey( bad_key, btc.get_version_byte(bad_key)) #in case the b58 check encoding is valid, we should #also check if the leading version byte is in the #expected set, and throw an error if not. if chr(btc.get_version_byte(bad_key)) not in '\x80\xef': raise Exception("Invalid version byte") #the bitcoin library should throw #if the compression byte is not there (test not needed #for secp256k1 branch since the wif_compressed function checks) if "decode_privkey" in dir(btc): if "compressed" in btc.get_privkey_format(bad_key) and \ btc.b58check_to_bin(x)[-1] != '\x01': raise Exception("Invalid compression byte")
def test_wif_privkeys_invalid(setup_keys): #first try to create wif privkey from key of wrong length bad_privs = ['\x01\x02'*17] #some silly private key but > 33 bytes #next try to create wif with correct length but wrong compression byte bad_privs.append('\x07'*32 + '\x02') for priv in bad_privs: with pytest.raises(Exception) as e_info: fake_wif = btc.wif_compressed_privkey(binascii.hexlify(priv)) #Create a wif with wrong length bad_wif1 = btc.bin_to_b58check('\x01\x02'*34, 128) #Create a wif with wrong compression byte bad_wif2 = btc.bin_to_b58check('\x07'*33, 128) for bw in [bad_wif1, bad_wif2]: with pytest.raises(Exception) as e_info: fake_priv = btc.from_wif_privkey(bw) #Some invalid b58 from bitcoin repo; #none of these are valid as any kind of key or address with open("test/base58_keys_invalid.json", "r") as f: json_data = f.read() invalid_key_list = json.loads(json_data) for k in invalid_key_list: bad_key = k[0] for netval in ["mainnet", "testnet"]: jm_single().config.set("BLOCKCHAIN", "network", netval) #if using py.test -s ; sanity check to see what's actually being tested print 'testing this key: ' + bad_key if "decode_privkey" in dir(btc): try: bad_key_format = btc.get_privkey_format(bad_key) print 'has correct format: ' + bad_key_format except: pass #should throw exception with pytest.raises(Exception) as e_info: if "decode_privkey" in dir(btc): from_wif_key = btc.decode_privkey(bad_key) else: from_wif_key = btc.from_wif_compressed_privkey( bad_key, btc.get_version_byte(bad_key)) #in case the b58 check encoding is valid, we should #also check if the leading version byte is in the #expected set, and throw an error if not. if chr(btc.get_version_byte(bad_key)) not in '\x80\xef': raise Exception("Invalid version byte") #the bitcoin library should throw #if the compression byte is not there (test not needed #for secp256k1 branch since the wif_compressed function checks) if "decode_privkey" in dir(btc): if "compressed" in btc.get_privkey_format(bad_key) and \ btc.b58check_to_bin(x)[-1] != '\x01': raise Exception("Invalid compression byte")
def test_blockr_estimate_fee(setup_blockr): absurd_fee = jm_single().config.getint("POLICY", "absurd_fee_per_kb") res = [] for N in [1, 3, 6]: res.append(jm_single().bc_interface.estimate_fee_per_kb(N)) assert res[0] >= res[2] #Note this can fail, it isn't very accurate. #assert res[1] >= res[2] #sanity checks: assert res[0] < absurd_fee assert res[2] < absurd_fee
def test_confirm_timeout(setup_tx_notify): txhex = make_tx_add_notify() jm_single().bc_interface.tick_forward_chain_interval = -1 jm_single().bc_interface.pushtx(txhex) time.sleep(10) jm_single().bc_interface.tick_forward_chain_interval = 2 assert unconfirm_called[0] assert not confirm_called[0] assert not timeout_unconfirm_called[0] assert timeout_confirm_called[0] return True
def test_blockr_estimate_fee(setup_blockr): absurd_fee = jm_single().config.getint("POLICY", "absurd_fee_per_kb") res = [] for N in [1,3,6]: res.append(jm_single().bc_interface.estimate_fee_per_kb(N)) assert res[0] >= res[2] #Note this can fail, it isn't very accurate. #assert res[1] >= res[2] #sanity checks: assert res[0] < absurd_fee assert res[2] < absurd_fee
def do_tx(wallet, amount): ins_full = wallet.select_utxos(0, amount) cj_addr = wallet.get_internal_addr(1) change_addr = wallet.get_internal_addr(0) wallet.update_cache_index() txid = make_sign_and_push(ins_full, wallet, amount, output_addr=cj_addr, change_addr=change_addr, estimate_fee=True) assert txid time.sleep(2) #blocks jm_single().bc_interface.sync_unspent(wallet)
def test_absurd_fees(setup_tx_creation): """Test triggering of ValueError exception if the transaction fees calculated from the blockchain interface exceed the limit set in the config. """ jm_single().bc_interface.absurd_fees = True #pay into it wallet = make_wallets(1, [[2, 0, 0, 0, 1]], 3)[0]['wallet'] jm_single().bc_interface.sync_wallet(wallet) amount = 350000000 ins_full = wallet.select_utxos(0, amount) with pytest.raises(ValueError) as e_info: txid = make_sign_and_push(ins_full, wallet, amount, estimate_fee=True)
def __init__(self, configdata, nick): super(DummyMC, self).__init__(configdata) #hacked in here to allow auth without mc-collection nick_priv = hashlib.sha256(os.urandom(16)).hexdigest() + '01' nick_pubkey = btc.privtopub(nick_priv) nick_pkh_raw = hashlib.sha256(nick_pubkey).digest()[:NICK_HASH_LENGTH] nick_pkh = btc.changebase(nick_pkh_raw, 256, 58) #right pad to maximum possible; b58 is not fixed length. #Use 'O' as one of the 4 not included chars in base58. nick_pkh += 'O' * (NICK_MAX_ENCODED - len(nick_pkh)) #The constructed length will be 1 + 1 + NICK_MAX_ENCODED nick = JOINMARKET_NICK_HEADER + str(jm_single().JM_VERSION) + nick_pkh jm_single().nickname = nick self.set_nick(nick, nick_priv, nick_pubkey)
def test_b58_valid_addresses(): with open("test/base58_keys_valid.json", "r") as f: json_data = f.read() valid_keys_list = json.loads(json_data) for a in valid_keys_list: addr, pubkey, prop_dict = a if not prop_dict["isPrivkey"]: if prop_dict["isTestnet"]: jm_single().config.set("BLOCKCHAIN", "network", "testnet") else: jm_single().config.set("BLOCKCHAIN", "network", "mainnet") #if using py.test -s ; sanity check to see what's actually being tested print 'testing this address: ' + addr res, message = validate_address(addr) assert res == True, "Incorrectly failed to validate address: " + addr + " with message: " + message
def oid_to_order(self, cjorder, oid, amount): total_amount = amount + cjorder.txfee mix_balance = self.wallet.get_balance_by_mixdepth() filtered_mix_balance = [ m for m in mix_balance.iteritems() if m[1] >= total_amount ] if not filtered_mix_balance: return None, None, None log.debug('mix depths that have enough, filtered_mix_balance = ' + str(filtered_mix_balance)) # use mix depth that has the closest amount of coins to what this transaction needs # keeps coins moving through mix depths more quickly # and its more likely to use txos of a similiar size to this transaction filtered_mix_balance = sorted( filtered_mix_balance, key=lambda x: x[1]) #sort smallest to largest usable amount log.debug('sorted order of filtered_mix_balance = ' + str(filtered_mix_balance)) mixdepth = filtered_mix_balance[0][0] log.debug('filling offer, mixdepth=' + str(mixdepth)) # mixdepth is the chosen depth we'll be spending from cj_addr = self.wallet.get_internal_addr( (mixdepth + 1) % self.wallet.max_mix_depth) change_addr = self.wallet.get_internal_addr(mixdepth) utxos = self.wallet.select_utxos(mixdepth, total_amount) my_total_in = sum([va['value'] for va in utxos.values()]) real_cjfee = calc_cj_fee(cjorder.ordertype, cjorder.cjfee, amount) change_value = my_total_in - amount - cjorder.txfee + real_cjfee if change_value <= jm_single().DUST_THRESHOLD: log.debug( 'change value=%d below dust threshold, finding new utxos' % (change_value)) try: utxos = self.wallet.select_utxos( mixdepth, total_amount + jm_single().DUST_THRESHOLD) except Exception: log.debug( 'dont have the required UTXOs to make a output above the dust threshold, quitting' ) return None, None, None return utxos, cj_addr, change_addr
def make_sign_and_push(ins_full, wallet, amount, output_addr=None, change_addr=None, hashcode=btc.SIGHASH_ALL): total = sum(x['value'] for x in ins_full.values()) ins = ins_full.keys() #random output address and change addr output_addr = wallet.get_new_addr(1, 1) if not output_addr else output_addr change_addr = wallet.get_new_addr(1, 0) if not change_addr else change_addr outs = [{'value': amount, 'address': output_addr}, {'value': total - amount - 100000, 'address': change_addr}] tx = btc.mktx(ins, outs) de_tx = btc.deserialize(tx) for index, ins in enumerate(de_tx['ins']): utxo = ins['outpoint']['hash'] + ':' + str(ins['outpoint']['index']) addr = ins_full[utxo]['address'] priv = wallet.get_key_from_addr(addr) if index % 2: priv = binascii.unhexlify(priv) tx = btc.sign(tx, index, priv, hashcode=hashcode) #pushtx returns False on any error print btc.deserialize(tx) push_succeed = jm_single().bc_interface.pushtx(tx) if push_succeed: return btc.txhash(tx) else: return False
def test_external_commitments(setup_podle): """Add this generated commitment to the external list {txid:N:{'P':pubkey, 'reveal':{1:{'P2':P2,'s':s,'e':e}, 2:{..},..}}} Note we do this *after* the sendpayment test so that the external commitments will not erroneously used (they are fake). """ ecs = {} tries = jm_single().config.getint("POLICY","taker_utxo_retries") for i in range(10): priv = os.urandom(32) dummy_utxo = btc.sha256(priv)+":2" ecs[dummy_utxo] = {} ecs[dummy_utxo]['reveal']={} for j in range(tries): P, P2, s, e, commit = generate_single_podle_sig(priv, j) if 'P' not in ecs[dummy_utxo]: ecs[dummy_utxo]['P']=P ecs[dummy_utxo]['reveal'][j] = {'P2':P2, 's':s, 'e':e} btc.add_external_commitments(ecs) used, external = btc.get_podle_commitments() for u in external: assert external[u]['P'] == ecs[u]['P'] for i in range(tries): for x in ['P2', 's', 'e']: assert external[u]['reveal'][str(i)][x] == ecs[u]['reveal'][i][x]
def add_external_commitments(utxo_datas): """Persist the PoDLE commitments for this utxo to the commitments.json file. The number of separate entries is dependent on the taker_utxo_retries entry, by default 3. """ 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']) ecs = {} for u, priv in utxo_datas: ecs[u] = {} ecs[u]['reveal']={} for j in range(jm_single().config.getint("POLICY", "taker_utxo_retries")): P, P2, s, e, commit = generate_single_podle_sig(u, priv, j) if 'P' not in ecs[u]: ecs[u]['P']=P ecs[u]['reveal'][j] = {'P2':P2, 's':s, 'e':e} btc.add_external_commitments(ecs)
def make_sign_and_push( ins_full, wallet, amount, output_addr=None, change_addr=None, hashcode=btc.SIGHASH_ALL, estimate_fee=False ): """Utility function for easily building transactions from wallets """ total = sum(x["value"] for x in ins_full.values()) ins = ins_full.keys() # random output address and change addr output_addr = wallet.get_new_addr(1, 1) if not output_addr else output_addr change_addr = wallet.get_new_addr(1, 0) if not change_addr else change_addr fee_est = estimate_tx_fee(len(ins), 2) if estimate_fee else 10000 outs = [{"value": amount, "address": output_addr}, {"value": total - amount - fee_est, "address": change_addr}] tx = btc.mktx(ins, outs) de_tx = btc.deserialize(tx) for index, ins in enumerate(de_tx["ins"]): utxo = ins["outpoint"]["hash"] + ":" + str(ins["outpoint"]["index"]) addr = ins_full[utxo]["address"] priv = wallet.get_key_from_addr(addr) if index % 2: priv = binascii.unhexlify(priv) tx = btc.sign(tx, index, priv, hashcode=hashcode) # pushtx returns False on any error print btc.deserialize(tx) push_succeed = jm_single().bc_interface.pushtx(tx) if push_succeed: return btc.txhash(tx) else: return False
def create_my_orders(self): mix_balance = self.wallet.get_balance_by_mixdepth() if len([b for m, b in mix_balance.iteritems() if b > 0]) == 0: log.debug('do not have any coins left') return [] # print mix_balance max_mix = max(mix_balance, key=mix_balance.get) f = '0' if self.ordertype == 'reloffer': f = self.cjfee_r #minimum size bumped if necessary such that you always profit #least 50% of the miner fee self.minsize = int(1.5 * self.txfee / float(self.cjfee_r)) elif ordertype == 'absoffer': f = str(self.txfee + self.cjfee_a) order = {'oid': 0, 'ordertype': self.ordertype, 'minsize': self.minsize, 'maxsize': mix_balance[max_mix] - max( jm_single().DUST_THRESHOLD,txfee), 'txfee': self.txfee, 'cjfee': f} # sanity check assert order['minsize'] >= 0 assert order['maxsize'] > 0 assert order['minsize'] <= order['maxsize'] return [order]
def finishcallback(self, coinjointx): if coinjointx.all_responded: jm_single().bc_interface.add_tx_notify( coinjointx.latest_tx, self.unconfirm_callback, self.confirm_callback, coinjointx.my_cj_addr) pushed = coinjointx.self_sign_and_push() if pushed: self.taker.wallet.remove_old_utxos(coinjointx.latest_tx) else: log.debug("Failed to push transaction, trying again.") self.create_tx() else: self.ignored_makers += coinjointx.nonrespondants log.debug('recreating the tx, ignored_makers=' + str( self.ignored_makers)) self.create_tx()
def pushtx(self): push_attempts = 3 while True: ret = self.taker.cjtx.push() if ret: break if push_attempts == 0: break push_attempts -= 1 time.sleep(10) if ret: jm_single().bc_interface.add_tx_notify( self.taker.cjtx.latest_tx, self.unconfirm_callback, self.confirm_callback, self.taker.cjtx.my_cj_addr, self.timeout_callback) return ret
def test_external_commitments(setup_podle): """Add this generated commitment to the external list {txid:N:{'P':pubkey, 'reveal':{1:{'P2':P2,'s':s,'e':e}, 2:{..},..}}} Note we do this *after* the sendpayment test so that the external commitments will not erroneously used (they are fake). """ ecs = {} tries = jm_single().config.getint("POLICY", "taker_utxo_retries") for i in range(10): priv = os.urandom(32) dummy_utxo = btc.sha256(priv) + ":2" ecs[dummy_utxo] = {} ecs[dummy_utxo]['reveal'] = {} for j in range(tries): P, P2, s, e, commit = generate_single_podle_sig(priv, j) if 'P' not in ecs[dummy_utxo]: ecs[dummy_utxo]['P'] = P ecs[dummy_utxo]['reveal'][j] = {'P2': P2, 's': s, 'e': e} btc.add_external_commitments(ecs) used, external = btc.get_podle_commitments() for u in external: assert external[u]['P'] == ecs[u]['P'] for i in range(tries): for x in ['P2', 's', 'e']: assert external[u]['reveal'][str( i)][x] == ecs[u]['reveal'][i][x]
def create_my_orders(self): mix_balance = self.wallet.get_balance_by_mixdepth() if len([b for m, b in mix_balance.iteritems() if b > 0]) == 0: log.debug('do not have any coins left') return [] # print mix_balance max_mix = max(mix_balance, key=mix_balance.get) f = '0' if ordertype == 'relorder': f = cjfee_r elif ordertype == 'absorder': f = str(txfee + cjfee_a) order = {'oid': 0, 'ordertype': ordertype, 'minsize': minsize, 'maxsize': mix_balance[max_mix] - jm_single().DUST_THRESHOLD, 'txfee': txfee, 'cjfee': f} # sanity check assert order['minsize'] >= 0 assert order['maxsize'] > 0 assert order['minsize'] <= order['maxsize'] return [order]
def create_my_orders(self): mix_balance = self.wallet.get_balance_by_mixdepth() if len([b for m, b in mix_balance.iteritems() if b > 0]) == 0: log.debug('do not have any coins left') return [] # print mix_balance max_mix = max(mix_balance, key=mix_balance.get) f = '0' if ordertype == 'reloffer': f = cjfee_r elif ordertype == 'absoffer': f = str(txfee + cjfee_a) order = { 'oid': 0, 'ordertype': ordertype, 'minsize': minsize, 'maxsize': mix_balance[max_mix] - max(jm_single().DUST_THRESHOLD, txfee), 'txfee': txfee, 'cjfee': f } # sanity check assert order['minsize'] >= 0 assert order['maxsize'] > 0 assert order['minsize'] <= order['maxsize'] return [order]