def open_wallet(path, ask_for_password=True, password=None, read_only=False, **kwargs): """ Open the wallet file at path and return the corresponding wallet object. params: path: str, full path to wallet file ask_for_password: bool, if False password is assumed unset and user will not be asked to type it password: password for storage, ignored if ask_for_password is True read_only: bool, if True, open wallet in read-only mode kwargs: additional options to pass to wallet's init method returns: wallet object """ if not os.path.isfile(path): raise Exception( "Failed to open wallet at '{}': not a file".format(path)) if not Storage.is_storage_file(path): raise Exception("Failed to open wallet at '{}': not a valid joinmarket" " wallet.\n\nIf this wallet is in the old json format " "you need to convert it using the conversion script" "at `scripts/convert_old_wallet.py`".format(path)) if ask_for_password and Storage.is_encrypted_storage_file(path): while True: try: # do not try empty password, assume unencrypted on empty password pwd = get_password( "Enter wallet decryption passphrase: ") or None storage = Storage(path, password=pwd, read_only=read_only) except StoragePasswordError: print("Wrong password, try again.") continue except Exception as e: print("Failed to load wallet, error message: " + repr(e)) raise e break else: storage = Storage(path, password, read_only=read_only) wallet_cls = get_wallet_cls_from_storage(storage) wallet = wallet_cls(storage, **kwargs) wallet_sanity_check(wallet) return wallet
def wallet_tool_main(wallet_root_path): """Main wallet tool script function; returned is a string (output or error) """ parser = get_wallettool_parser() (options, args) = parser.parse_args() walletclass = SegwitWallet if jm_single().config.get( "POLICY", "segwit") == "true" else Wallet # if the index_cache stored in wallet.json is longer than the default # then set maxmixdepth to the length of index_cache maxmixdepth_configured = True if not options.maxmixdepth: maxmixdepth_configured = False options.maxmixdepth = 5 noseed_methods = ['generate', 'recover'] methods = ['display', 'displayall', 'summary', 'showseed', 'importprivkey', 'history', 'showutxos'] methods.extend(noseed_methods) noscan_methods = ['showseed', 'importprivkey', 'dumpprivkey', 'signmessage'] if len(args) < 1: parser.error('Needs a wallet file or method') sys.exit(0) if args[0] in noseed_methods: method = args[0] else: seed = args[0] method = ('display' if len(args) == 1 else args[1].lower()) if not os.path.exists(os.path.join(wallet_root_path, seed)): wallet = walletclass(seed, None, options.maxmixdepth, options.gaplimit, extend_mixdepth= not maxmixdepth_configured, storepassword=(method == 'importprivkey'), wallet_dir=wallet_root_path) else: while True: try: pwd = get_password("Enter wallet decryption passphrase: ") wallet = walletclass(seed, pwd, options.maxmixdepth, options.gaplimit, extend_mixdepth=not maxmixdepth_configured, storepassword=(method == 'importprivkey'), wallet_dir=wallet_root_path) except WalletError: print("Wrong password, try again.") continue except Exception as e: print("Failed to load wallet, error message: " + repr(e)) sys.exit(0) break if method not in noscan_methods: # if nothing was configured, we override bitcoind's options so that # unconfirmed balance is included in the wallet display by default if 'listunspent_args' not in jm_single().config.options('POLICY'): jm_single().config.set('POLICY','listunspent_args', '[0]') sync_wallet(wallet, fast=options.fastsync) #Now the wallet/data is prepared, execute the script according to the method if method == "display": return wallet_display(wallet, options.gaplimit, options.showprivkey) elif method == "displayall": return wallet_display(wallet, options.gaplimit, options.showprivkey, displayall=True) elif method == "summary": return wallet_display(wallet, options.gaplimit, options.showprivkey, summarized=True) elif method == "history": if not isinstance(jm_single().bc_interface, BitcoinCoreInterface): print('showing history only available when using the Bitcoin Core ' + 'blockchain interface') sys.exit(0) else: return wallet_fetch_history(wallet, options) elif method == "generate": retval = wallet_generate_recover("generate", wallet_root_path) return retval if retval else "Failed" elif method == "recover": retval = wallet_generate_recover("recover", wallet_root_path) return retval if retval else "Failed" elif method == "showutxos": return wallet_showutxos(wallet, options.showprivkey) elif method == "showseed": return wallet_showseed(wallet) elif method == "dumpprivkey": return wallet_dumpprivkey(wallet, options.hd_path) elif method == "importprivkey": #note: must be interactive (security) wallet_importprivkey(wallet, options.mixdepth) return "Key import completed." elif method == "signmessage": return wallet_signmessage(wallet, options.hd_path, args[1])
if __name__ == "__main__": load_coinjoinxt_config() wallet_name = sys.argv[1] serv, port = sys.argv[2:4] max_mix_depth = 3 wallet_dir = os.path.join(cjxt_single().homedir, 'wallets') if not os.path.exists(os.path.join(wallet_dir, wallet_name)): wallet = SegwitWallet(wallet_name, None, max_mix_depth, 6, wallet_dir=wallet_dir) else: while True: try: pwd = get_password("Enter wallet decryption passphrase: ") wallet = SegwitWallet(wallet_name, pwd, max_mix_depth, 6, wallet_dir=wallet_dir) except WalletError: print("Wrong password, try again.") continue except Exception as e: print("Failed to load wallet, error message: " + repr(e)) sys.exit(0) break """Uncomment this for auto-funding on regtest. if isinstance(cjxt_single().bc_interface, RegtestBitcoinCoreInterface): #funding the wallet with outputs specifically suitable for the starting point.
def main(): tumble_log = get_tumble_log(logsdir) (options, args) = get_tumbler_parser().parse_args() options = vars(options) if len(args) < 1: parser.error('Needs a wallet file') sys.exit(0) load_program_config() #Load the wallet wallet_name = args[0] max_mix_depth = options['mixdepthsrc'] + options['mixdepthcount'] if not os.path.exists(os.path.join('wallets', wallet_name)): wallet = SegwitWallet(wallet_name, None, max_mix_depth) else: while True: try: pwd = get_password("Enter wallet decryption passphrase: ") wallet = SegwitWallet(wallet_name, pwd, max_mix_depth) except WalletError: print("Wrong password, try again.") continue except Exception as e: print("Failed to load wallet, error message: " + repr(e)) sys.exit(0) break if jm_single().config.get("BLOCKCHAIN", "blockchain_source") == "electrum-server": jm_single().bc_interface.synctype = "with-script" sync_wallet(wallet, fast=options['fastsync']) #Parse options and generate schedule #Output information to log files jm_single().mincjamount = options['mincjamount'] destaddrs = args[1:] print(destaddrs) #If the --restart flag is set we read the schedule #from the file, and filter out entries that are #already complete if options['restart']: res, schedule = get_schedule( os.path.join(logsdir, options['schedulefile'])) if not res: print("Failed to load schedule, name: " + str(options['schedulefile'])) print("Error was: " + str(schedule)) sys.exit(0) #This removes all entries that are marked as done schedule = [s for s in schedule if s[5] != 1] if isinstance(schedule[0][5], str) and len(schedule[0][5]) == 64: #ensure last transaction is confirmed before restart tumble_log.info("WAITING TO RESTART...") txid = schedule[0][5] restart_waiter(txid + ":0") #add 0 index because all have it #remove the already-done entry (this connects to the other TODO, #probably better *not* to truncate the done-already txs from file, #but simplest for now. schedule = schedule[1:] elif schedule[0][5] != 0: print("Error: first schedule entry is invalid.") sys.exit(0) with open(os.path.join(logsdir, options['schedulefile']), "wb") as f: f.write(schedule_to_text(schedule)) tumble_log.info("TUMBLE RESTARTING") else: #Create a new schedule from scratch schedule = get_tumble_schedule(options, destaddrs) tumble_log.info("TUMBLE STARTING") with open(os.path.join(logsdir, options['schedulefile']), "wb") as f: f.write(schedule_to_text(schedule)) print("Schedule written to logs/" + options['schedulefile']) tumble_log.info("With this schedule: ") tumble_log.info(pprint.pformat(schedule)) print("Progress logging to logs/TUMBLE.log") def filter_orders_callback(orders_fees, cjamount): """Decide whether to accept fees """ return tumbler_filter_orders_callback(orders_fees, cjamount, taker, options) def taker_finished(res, fromtx=False, waittime=0.0, txdetails=None): """on_finished_callback for tumbler; processing is almost entirely deferred to generic taker_finished in tumbler_support module, except here reactor signalling. """ sfile = os.path.join(logsdir, options['schedulefile']) tumbler_taker_finished_update(taker, sfile, tumble_log, options, res, fromtx, waittime, txdetails) if not fromtx: reactor.stop() elif fromtx != "unconfirmed": reactor.callLater(waittime * 60, clientfactory.getClient().clientStart) #to allow testing of confirm/unconfirm callback for multiple txs if isinstance(jm_single().bc_interface, RegtestBitcoinCoreInterface): jm_single().bc_interface.tick_forward_chain_interval = 10 jm_single().bc_interface.simulating = True jm_single().maker_timeout_sec = 15 #instantiate Taker with given schedule and run taker = Taker(wallet, schedule, order_chooser=weighted_order_choose, callbacks=(filter_orders_callback, None, taker_finished), tdestaddrs=destaddrs) clientfactory = JMClientProtocolFactory(taker) nodaemon = jm_single().config.getint("DAEMON", "no_daemon") daemon = True if nodaemon == 1 else False if jm_single().config.get("BLOCKCHAIN", "network") in ["regtest", "testnet"]: startLogging(sys.stdout) start_reactor(jm_single().config.get("DAEMON", "daemon_host"), jm_single().config.getint("DAEMON", "daemon_port"), clientfactory, daemon=daemon)
def main(): parser = get_sendpayment_parser() (options, args) = parser.parse_args() load_program_config() if options.schedule == '' and len(args) < 3: parser.error('Needs a wallet, amount and destination address') sys.exit(0) #without schedule file option, use the arguments to create a schedule #of a single transaction sweeping = False if options.schedule == '': #note that sendpayment doesn't support fractional amounts, fractions throw #here. amount = int(args[1]) if amount == 0: sweeping = True destaddr = args[2] mixdepth = options.mixdepth addr_valid, errormsg = validate_address(destaddr) if not addr_valid: print('ERROR: Address invalid. ' + errormsg) return schedule = [[ options.mixdepth, amount, options.makercount, destaddr, 0.0, 0 ]] else: result, schedule = get_schedule(options.schedule) if not result: log.info( "Failed to load schedule file, quitting. Check the syntax.") log.info("Error was: " + str(schedule)) sys.exit(0) mixdepth = 0 for s in schedule: if s[1] == 0: sweeping = True #only used for checking the maximum mixdepth required mixdepth = max([mixdepth, s[0]]) wallet_name = args[0] #to allow testing of confirm/unconfirm callback for multiple txs if isinstance(jm_single().bc_interface, RegtestBitcoinCoreInterface): jm_single().bc_interface.tick_forward_chain_interval = 10 jm_single().bc_interface.simulating = True jm_single().maker_timeout_sec = 15 chooseOrdersFunc = None if options.pickorders: chooseOrdersFunc = pick_order if sweeping: print('WARNING: You may have to pick offers multiple times') print('WARNING: due to manual offer picking while sweeping') elif options.choosecheapest: chooseOrdersFunc = cheapest_order_choose else: # choose randomly (weighted) chooseOrdersFunc = weighted_order_choose # Dynamically estimate a realistic fee if it currently is the default value. # At this point we do not know even the number of our own inputs, so # we guess conservatively with 2 inputs and 2 outputs each. if options.txfee == -1: options.txfee = max(options.txfee, estimate_tx_fee(2, 2, txtype="p2sh-p2wpkh")) log.debug("Estimated miner/tx fee for each cj participant: " + str(options.txfee)) assert (options.txfee >= 0) log.debug('starting sendpayment') if not options.userpcwallet: #maxmixdepth in the wallet is actually the *number* of mixdepths (so misnamed); #to ensure we have enough, must be at least (requested index+1) max_mix_depth = max([mixdepth + 1, options.amtmixdepths]) if not os.path.exists(os.path.join('wallets', wallet_name)): wallet = get_wallet_cls()(wallet_name, None, max_mix_depth, options.gaplimit) else: while True: try: pwd = get_password("Enter wallet decryption passphrase: ") wallet = get_wallet_cls()(wallet_name, pwd, max_mix_depth, options.gaplimit) except WalletError: print("Wrong password, try again.") continue except Exception as e: print("Failed to load wallet, error message: " + repr(e)) sys.exit(0) break else: wallet = BitcoinCoreWallet(fromaccount=wallet_name) if jm_single().config.get( "BLOCKCHAIN", "blockchain_source" ) == "electrum-server" and options.makercount != 0: jm_single().bc_interface.synctype = "with-script" #wallet sync will now only occur on reactor start if we're joining. sync_wallet(wallet, fast=options.fastsync) if options.makercount == 0: if isinstance(wallet, BitcoinCoreWallet): raise NotImplementedError( "Direct send only supported for JM wallets") direct_send(wallet, amount, mixdepth, destaddr, options.answeryes) return def filter_orders_callback(orders_fees, cjamount): orders, total_cj_fee = orders_fees log.info("Chose these orders: " + pprint.pformat(orders)) log.info('total cj fee = ' + str(total_cj_fee)) total_fee_pc = 1.0 * total_cj_fee / cjamount log.info('total coinjoin fee = ' + str(float('%.3g' % (100.0 * total_fee_pc))) + '%') WARNING_THRESHOLD = 0.02 # 2% if total_fee_pc > WARNING_THRESHOLD: log.info('\n'.join(['=' * 60] * 3)) log.info('WARNING ' * 6) log.info('\n'.join(['=' * 60] * 1)) log.info( 'OFFERED COINJOIN FEE IS UNUSUALLY HIGH. DOUBLE/TRIPLE CHECK.') log.info('\n'.join(['=' * 60] * 1)) log.info('WARNING ' * 6) log.info('\n'.join(['=' * 60] * 3)) if not options.answeryes: if raw_input('send with these orders? (y/n):')[0] != 'y': return False return True def taker_finished(res, fromtx=False, waittime=0.0, txdetails=None): if fromtx == "unconfirmed": #If final entry, stop *here*, don't wait for confirmation if taker.schedule_index + 1 == len(taker.schedule): reactor.stop() return if fromtx: if res: txd, txid = txdetails taker.wallet.remove_old_utxos(txd) taker.wallet.add_new_utxos(txd, txid) reactor.callLater(waittime * 60, clientfactory.getClient().clientStart) else: #a transaction failed; just stop reactor.stop() else: if not res: log.info("Did not complete successfully, shutting down") #Should usually be unreachable, unless conf received out of order; #because we should stop on 'unconfirmed' for last (see above) else: log.info("All transactions completed correctly") reactor.stop() taker = Taker(wallet, schedule, order_chooser=chooseOrdersFunc, callbacks=(filter_orders_callback, None, taker_finished)) clientfactory = JMClientProtocolFactory(taker) nodaemon = jm_single().config.getint("DAEMON", "no_daemon") daemon = True if nodaemon == 1 else False if jm_single().config.get("BLOCKCHAIN", "network") in ["regtest", "testnet"]: startLogging(sys.stdout) start_reactor(jm_single().config.get("DAEMON", "daemon_host"), jm_single().config.getint("DAEMON", "daemon_port"), clientfactory, daemon=daemon)
def main_cs(test_data=None): #twisted logging (TODO disable for non-debug runs) if test_data: wallet_name, args, options, use_ssl, alt_class, alt_c_class, fail_alice_state, fail_carol_state = test_data server, port, usessl = parse_server_string(options.serverport) else: parser = get_coinswap_parser() (options, args) = parser.parse_args() #Will only be used by client server, port, usessl = parse_server_string(options.serverport) if options.checkonly: #no need for any more data; just query alice_client = CoinSwapJSONRPCClient(server[2:], port, usessl=usessl) reactor.callWhenRunning(alice_client.send_poll_unsigned, "status", print_status) reactor.run() return log.startLogging(sys.stdout) load_coinswap_config() wallet_name = args[0] #depth 0: spend in, depth 1: receive out, depth 2: for backout transactions. max_mix_depth = 3 wallet_dir = os.path.join(cs_single().homedir, 'wallets') if not os.path.exists(os.path.join(wallet_dir, wallet_name)): wallet = SegwitWallet(wallet_name, None, max_mix_depth, 6, wallet_dir=wallet_dir) else: while True: try: pwd = get_password("Enter wallet decryption passphrase: ") wallet = SegwitWallet(wallet_name, pwd, max_mix_depth, 6, wallet_dir=wallet_dir) except WalletError: print("Wrong password, try again.") continue except Exception as e: print("Failed to load wallet, error message: " + repr(e)) sys.exit(0) break #for testing main script (not test framework), need funds. if not test_data and isinstance(cs_single().bc_interface, RegtestBitcoinCoreInterface): for i in range(3): cs_single().bc_interface.grab_coins( wallet.get_new_addr(0, 0, True), 2.0) wallet.index[0][0] -= 3 time.sleep(3) sync_wallet(wallet, fast=options.fastsync) if test_data: cs_single().bc_interface.wallet_synced = True wallet.used_coins = None if options.serve: #sanity check that client params were not provided: if len(args) > 1: print("Extra parameters provided for running as server. " "Are you sure you didn't want to run as client?") sys.exit(0) if not test_data: main_server(options, wallet) else: main_server( options, wallet, { 'use_ssl': use_ssl, 'alt_c_class': alt_c_class, 'fail_carol_state': fail_carol_state }) return wallet.get_balance_by_mixdepth() return if not options.recover: target_amount = int(args[1]) #Reset the targetting for backout transactions #TODO must be removed/changed for updated fees handling oldtarget = cs_single().config.get("POLICY", "tx_fees") newtarget = cs_single().config.getint("POLICY", "backout_fee_target") multiplier = float(cs_single().config.get("POLICY", "backout_fee_multiplier")) cs_single().config.set("POLICY", "tx_fees", str(newtarget)) tx23fee = estimate_tx_fee((1, 2, 2), 1, txtype='p2shMofN') tx23fee = int(multiplier * tx23fee) tx24_recipient_amount = target_amount - tx23fee tx35_recipient_amount = target_amount - tx23fee cs_single().config.set("POLICY", "tx_fees", oldtarget) #to allow testing of confirm/unconfirm callback for multiple txs if isinstance(cs_single().bc_interface, RegtestBitcoinCoreInterface): cs_single().bc_interface.tick_forward_chain_interval = 2 cs_single().bc_interface.simulating = True cs_single().config.set("BLOCKCHAIN", "notify_port", "62652") cs_single().config.set("BLOCKCHAIN", "rpc_host", "127.0.0.2") #if restart option selected, read state and backout if options.recover: session_id = options.recover alice = CoinSwapAlice(wallet, 'alicestate') alice.bbmb = wallet.get_balance_by_mixdepth(verbose=False) alice.load(sessionid=session_id) alice.backout("Recovering from shutdown") reactor.run() return if len(args) > 2: tx5address = args[2] if not validate_address(tx5address): print("Invalid address: ", tx5address) sys.exit(0) else: #Our destination address should be in a separate mixdepth tx5address = wallet.get_new_addr(1, 1, True) #instantiate the parameters, but don't yet have the ephemeral pubkeys #or destination addresses. #TODO figure out best estimate incl. priority btcfee_est = estimate_tx_fee((1, 2, 2), 1, txtype='p2shMofN') cpp = CoinSwapPublicParameters(base_amount=target_amount, bitcoin_fee=btcfee_est) cpp.set_addr_data(addr5=tx5address) testing_mode = True if test_data else False aliceclass = alt_class if test_data and alt_class else CoinSwapAlice if test_data and fail_alice_state: alice = aliceclass(wallet, 'alicestate', cpp, testing_mode=testing_mode, fail_state=fail_alice_state) else: if testing_mode or options.checkfee: alice = aliceclass(wallet, 'alicestate', cpp, testing_mode=testing_mode) else: alice = aliceclass(wallet, 'alicestate', cpp, testing_mode=testing_mode, fee_checker="cli") alice_client = CoinSwapJSONRPCClient(server[2:], port, alice.sm.tick, alice.backout, usessl) alice.set_jsonrpc_client(alice_client) reactor.callWhenRunning(alice_client.send_poll_unsigned, "status", alice.check_server_status) if not test_data: reactor.run() if test_data: return alice
def main_cs(test_data=None): #twisted logging (TODO disable for non-debug runs) if test_data: wallet_name, args, options, use_ssl, alt_class, alt_c_class = test_data else: log.startLogging(sys.stdout) #Joinmarket wallet parser = get_coinswap_parser() (options, args) = parser.parse_args() load_coinswap_config() wallet_name = args[0] #depth 0: spend in, depth 1: receive out, depth 2: for backout transactions. max_mix_depth = 3 if not os.path.exists(os.path.join('wallets', wallet_name)): wallet = Wallet(wallet_name, None, max_mix_depth, 6) else: while True: try: pwd = get_password("Enter wallet decryption passphrase: ") wallet = Wallet(wallet_name, pwd, max_mix_depth, 6) except WalletError: print("Wrong password, try again.") continue except Exception as e: print("Failed to load wallet, error message: " + repr(e)) sys.exit(0) break #for testing main script (not test framework), need funds. if not test_data and isinstance(cs_single().bc_interface, RegtestBitcoinCoreInterface): cs_single().bc_interface.grab_coins(wallet.get_new_addr(0, 0), 2.0) time.sleep(3) sync_wallet(wallet, fast=options.fastsync) wallet.used_coins = None if options.serve: #sanity check that client params were not provided: if len(args) > 1: print("Extra parameters provided for running as server. " "Are you sure you didn't want to run as client?") sys.exit(0) if not test_data: main_server(options, wallet) else: main_server(options, wallet, { 'use_ssl': use_ssl, 'alt_c_class': alt_c_class }) return wallet.get_balance_by_mixdepth() return tx01_amount = int(args[1]) #Reset the targetting for backout transactions oldtarget = cs_single().config.get("POLICY", "tx_fees") newtarget = cs_single().config.getint("POLICY", "backout_fee_target") multiplier = float(cs_single().config.get("POLICY", "backout_fee_multiplier")) cs_single().config.set("POLICY", "tx_fees", str(newtarget)) tx23fee = estimate_tx_fee((1, 2, 2), 1, txtype='p2shMofN') tx23fee = int(multiplier * tx23fee) tx24_recipient_amount = tx01_amount - tx23fee tx35_recipient_amount = tx01_amount - tx23fee cs_single().config.set("POLICY", "tx_fees", oldtarget) #to allow testing of confirm/unconfirm callback for multiple txs if isinstance(cs_single().bc_interface, RegtestBitcoinCoreInterface): cs_single().bc_interface.tick_forward_chain_interval = 2 cs_single().bc_interface.simulating = True cs_single().config.set("BLOCKCHAIN", "notify_port", "62652") cs_single().config.set("BLOCKCHAIN", "rpc_host", "127.0.0.2") #if restart option selected, read state and backout if options.recover: session_id = options.recover alice = CoinSwapAlice(wallet, 'alicestate') alice.bbmb = wallet.get_balance_by_mixdepth(verbose=False) alice.load(sessionid=session_id) alice.backout("Recovering from shutdown") reactor.run() return if len(args) > 2: tx5address = args[2] if not validate_address(tx5address): print("Invalid address: ", tx5address) sys.exit(0) else: #Our destination address should be in a separate mixdepth tx5address = wallet.get_new_addr(1, 1) #instantiate the parameters, but don't yet have the ephemeral pubkeys #or destination addresses. cpp = CoinSwapPublicParameters(tx01_amount, tx24_recipient_amount, tx35_recipient_amount) #Alice must set the unique identifier for this run. cpp.set_session_id() cpp.set_tx5_address(tx5address) testing_mode = True if test_data else False aliceclass = alt_class if test_data and alt_class else CoinSwapAlice alice = aliceclass(wallet, 'alicestate', cpp, testing_mode=testing_mode) scheme, server, port = options.serverport.split(":") print("got this scheme, server, port: ", scheme, server, port) if scheme == "https": usessl = True elif scheme == "http": usessl = False else: print("Invalid server string: ", options.serverport) sys.exit(0) if not server[:2] == "//": print("Invalid server string: ", options.serverport) alice_client = CoinSwapJSONRPCClient(server[2:], port, alice.sm.tick, alice.backout, usessl) alice.set_jsonrpc_client(alice_client) reactor.callWhenRunning(alice.sm.tick) if not test_data: reactor.run() if test_data: return alice