def init_daemon(config_options): config = SimpleConfig(config_options) wallet_path = config.get_wallet_path() if not WalletStorage.files_are_matched_by_path(wallet_path): print("Error: Wallet file not found.") print("Type 'electrum-sv create' to create a new wallet, " "or provide a path to a wallet with the -w option") sys.exit(0) storage = WalletStorage(wallet_path) if storage.is_encrypted(): if 'wallet_password' in config_options: print( 'Warning: unlocking wallet with commandline argument \"--walletpassword\"' ) password = config_options['wallet_password'] elif config.get('password'): password = config.get('password') else: password = prompt_password('Password:'******'password'] = password
def test_can_set_options_set_in_user_config(self): another_path = tempfile.mkdtemp() fake_read_user = lambda _: {"electrum_sv_path": self.electrum_dir} read_user_dir = lambda: self.user_dir config = SimpleConfig(options={}, read_user_config_function=fake_read_user, read_user_dir_function=read_user_dir) config.set_key("electrum_sv_path", another_path) self.assertEqual(another_path, config.get("electrum_sv_path"))
def test_cannot_set_options_passed_by_command_line(self): fake_read_user = lambda _: {"electrum_sv_path": "b"} read_user_dir = lambda: self.user_dir config = SimpleConfig(options=self.options, read_user_config_function=fake_read_user, read_user_dir_function=read_user_dir) config.set_key("electrum_sv_path", "c") self.assertEqual(self.options.get("electrum_sv_path"), config.get("electrum_sv_path"))
def test_simple_config_user_config_is_used_if_others_arent_specified(self): """If no system-wide configuration and no command-line options are specified, the user configuration is used instead.""" fake_read_user = lambda _: {"electrum_sv_path": self.electrum_dir} read_user_dir = lambda: self.user_dir config = SimpleConfig(options={}, read_user_config_function=fake_read_user, read_user_dir_function=read_user_dir) self.assertEqual(self.options.get("electrum_sv_path"), config.get("electrum_sv_path"))
def test_simple_config_command_line_overrides_everything(self): """Options passed by command line override all other configuration sources""" fake_read_user = lambda _: {"electrum_sv_path": "b"} read_user_dir = lambda: self.user_dir config = SimpleConfig(options=self.options, read_user_config_function=fake_read_user, read_user_dir_function=read_user_dir) self.assertEqual(self.options.get("electrum_sv_path"), config.get("electrum_sv_path"))
def init_cmdline(config_options, server): config = SimpleConfig(config_options) cmdname = config.get('cmd') cmd = known_commands[cmdname] if cmdname == 'signtransaction' and config.get('privkey'): cmd.requires_wallet = False cmd.requires_password = False if cmdname in ['payto', 'paytomany'] and config.get('unsigned'): cmd.requires_password = False if cmdname in ['payto', 'paytomany'] and config.get('broadcast'): cmd.requires_network = True wallet_path = config.get_wallet_path() if cmd.requires_wallet and not WalletStorage.files_are_matched_by_path( wallet_path): print("Error: Wallet file not found.") print("Type 'electrum-sv create' to create a new wallet, " "or provide a path to a wallet with the -w option") sys.exit(0) # instantiate wallet for command-line storage = WalletStorage(wallet_path) # important warning if cmd.name in ['getprivatekeys']: print("WARNING: ALL your private keys are secret.", file=sys.stderr) print( "Exposing a single private key can compromise your entire wallet!", file=sys.stderr) print( "In particular, DO NOT use 'redeem private key' services " "proposed by third parties.", file=sys.stderr) # commands needing password if ((cmd.requires_wallet and storage.is_encrypted() and server is None) or (cmd.requires_password and (storage.get('use_encryption') or storage.is_encrypted()))): if config.get('password'): password = config.get('password') else: password = prompt_password('Password:'******'password'] = password if cmd.name == 'password': new_password = prompt_password('New password:'******'new_password'] = new_password return cmd, password
def test_user_config_is_not_written_with_read_only_config(self): """The user config does not contain command-line options when saved.""" fake_read_user = lambda _: {"something": "a"} read_user_dir = lambda: self.user_dir self.options.update({"something": "c"}) config = SimpleConfig(options=self.options, read_user_config_function=fake_read_user, read_user_dir_function=read_user_dir) config.save_user_config() contents = None with open(os.path.join(self.electrum_dir, "config"), "r") as f: contents = f.read() result = ast.literal_eval(contents) result.pop('config_version', None) self.assertEqual({"something": "a"}, result)
def get_peers(): config = SimpleConfig() peers = {} # 1. get connected interfaces server = config.get('server') interfaces = get_interfaces([server]) if not interfaces: print("No connection to", server) return [] # 2. get list of peers interface = interfaces[server] interface.queue_request('server.peers.subscribe', [], 0) responses = wait_on_interfaces(interfaces).get(server) if responses: response = responses[0][1] # One response, (req, response) tuple peers = parse_servers(response.get('result')) return peers
def test_simple_config_key_rename(self): """auto_cycle was renamed auto_connect""" fake_read_user = lambda _: {"auto_cycle": True} read_user_dir = lambda: self.user_dir config = SimpleConfig(options=self.options, read_user_config_function=fake_read_user, read_user_dir_function=read_user_dir) self.assertEqual(config.get("auto_connect"), True) self.assertEqual(config.get("auto_cycle"), None) fake_read_user = lambda _: {"auto_connect": False, "auto_cycle": True} config = SimpleConfig(options=self.options, read_user_config_function=fake_read_user, read_user_dir_function=read_user_dir) self.assertEqual(config.get("auto_connect"), False) self.assertEqual(config.get("auto_cycle"), None)
def get_interfaces(servers, timeout=10): '''Returns a map of servers to connected interfaces. If any connections fail or timeout, they will be missing from the map. ''' socket_queue = queue.Queue() config = SimpleConfig() connecting = {} for server in servers: if server not in connecting: connecting[server] = Connection(server, socket_queue, config.path) interfaces = {} timeout = time.time() + timeout count = 0 while time.time() < timeout and count < len(servers): try: server, socket = socket_queue.get(True, 0.3) except queue.Empty: continue if socket: interfaces[server] = Interface(server, socket) count += 1 return interfaces
def main(): # The hook will only be used in the Qt GUI right now setup_thread_excepthook() # on osx, delete Process Serial Number arg generated for apps launched in Finder sys.argv = [x for x in sys.argv if not x.startswith('-psn')] # old 'help' syntax if len(sys.argv) > 1 and sys.argv[1] == 'help': sys.argv.remove('help') sys.argv.append('-h') # read arguments from stdin pipe and prompt for i, arg in enumerate(sys.argv): if arg == '-': if not sys.stdin.isatty(): sys.argv[i] = sys.stdin.read() break else: raise Exception('Cannot get argument from stdin') elif arg == '?': sys.argv[i] = input("Enter argument:") elif arg == ':': sys.argv[i] = prompt_password('Enter argument (will not echo):', False) # parse command line parser = get_parser() args = parser.parse_args() # config is an object passed to the various constructors (wallet, interface, gui) config_options = args.__dict__ config_options = { key: value for key, value in config_options.items() if value is not None and key not in config_variables.get(args.cmd, {}) } logs.set_level(config_options['verbose']) if config_options.get('server'): config_options['auto_connect'] = False config_options['cwd'] = os.getcwd() # fixme: this can probably be achieved with a runtime hook (pyinstaller) try: if is_bundle and os.path.exists( os.path.join(sys._MEIPASS, 'is_portable')): config_options['portable'] = True except AttributeError: config_options['portable'] = False if config_options.get('portable'): config_options['electrum_sv_path'] = os.path.join( os.path.dirname(os.path.realpath(__file__)), 'electrum_sv_data') if config_options.get('file_logging'): log_path = os.path.join(platform.user_dir(prefer_local=True), "logs") os.makedirs(log_path, exist_ok=True) log_path = os.path.join(log_path, time.strftime("%Y%m%d-%H%M%S") + ".log") logs.add_file_output(log_path) if config_options.get('testnet'): Net.set_to(SVTestnet) # check uri uri = config_options.get('url') if uri: if not web.is_URI(uri): print('unknown command:', uri, file=sys.stderr) sys.exit(1) config_options['url'] = uri # todo: defer this to gui config = SimpleConfig(config_options) cmdname = config.get('cmd') # Set the app state proxy if cmdname == 'gui': try: from electrumsv.gui.qt.app_state import QtAppStateProxy except ImportError as e: platform.missing_import(e) QtAppStateProxy(config, 'qt') else: AppStateProxy(config, 'cmdline') # run non-RPC commands separately if cmdname in ['create', 'restore']: run_non_RPC(config) sys.exit(0) if cmdname == 'gui': fd, server = daemon.get_fd_or_server(config) if fd is not None: d = daemon.Daemon(fd, True) d.start() app_state.app.run_gui() sys.exit(0) else: result = server.gui(config_options) elif cmdname == 'daemon': subcommand = config.get('subcommand') if subcommand in ['load_wallet']: init_daemon(config_options) if subcommand in [None, 'start']: fd, server = daemon.get_fd_or_server(config) if fd is not None: if subcommand == 'start': pid = os.fork() if pid: print("starting daemon (PID %d)" % pid, file=sys.stderr) sys.exit(0) d = daemon.Daemon(fd, False) d.start() if config.get('websocket_server'): try: from electrumsv import websockets except ImportError as e: platform.missing_import(e) websockets.WebSocketServer(config, d.network).start() if config.get('requests_dir'): path = os.path.join(config.get('requests_dir'), 'index.html') if not os.path.exists(path): print("Requests directory not configured.") print("You can configure it using " "https://github.com/spesmilo/electrum-merchant") sys.exit(1) d.join() sys.exit(0) else: result = server.daemon(config_options) else: server = daemon.get_server(config) if server is not None: result = server.daemon(config_options) else: print("Daemon not running") sys.exit(1) else: # command line server = daemon.get_server(config) init_cmdline(config_options, server) if server is not None: result = server.run_cmdline(config_options) else: cmd = known_commands[cmdname] if cmd.requires_network: print("Daemon not running; try 'electrum-sv daemon start'") sys.exit(1) else: result = run_offline_command(config, config_options) # print result if isinstance(result, str): print(result) elif type(result) is dict and result.get('error'): print(result.get('error'), file=sys.stderr) elif result is not None: print(json_encode(result)) sys.exit(0)
def main(): enforce_requirements() setup_windows_console() # The hook will only be used in the Qt GUI right now setup_thread_excepthook() # on osx, delete Process Serial Number arg generated for apps launched in Finder sys.argv = [x for x in sys.argv if not x.startswith('-psn')] # old 'help' syntax if len(sys.argv) > 1 and sys.argv[1] == 'help': sys.argv.remove('help') sys.argv.append('-h') # read arguments from stdin pipe and prompt for i, arg in enumerate(sys.argv): if arg == '-': if not sys.stdin.isatty(): sys.argv[i] = sys.stdin.read() break else: raise Exception('Cannot get argument from stdin') elif arg == '?': sys.argv[i] = input("Enter argument:") elif arg == ':': sys.argv[i] = prompt_password('Enter argument (will not echo):', False) # parse command line parser = get_parser() args = parser.parse_args() # config is an object passed to various constructors config_options = args.__dict__ config_options = { key: value for key, value in config_options.items() if value is not None and key not in config_variables.get(args.cmd, {}) } logs.set_level(config_options['verbose']) if config_options.get('server'): config_options['auto_connect'] = False config_options['cwd'] = os.getcwd() # fixme: this can probably be achieved with a runtime hook (pyinstaller) portable_base_path = None try: if startup.is_bundle and os.path.exists( os.path.join(sys._MEIPASS, 'is_portable')): config_options['portable'] = True # Ensure the wallet data is stored in the same directory as the executable. portable_base_path = os.path.dirname(sys.executable) except AttributeError: config_options['portable'] = False if config_options.get('portable'): if portable_base_path is None: # Default to the same directory the 'electrum-sv' script is in. portable_base_path = os.path.dirname(os.path.realpath(sys.argv[0])) config_options['electrum_sv_path'] = os.path.join( portable_base_path, 'electrum_sv_data') if config_options.get('file_logging'): if config_options.get('portable'): log_path = os.path.join(config_options['electrum_sv_path'], "logs") else: log_path = os.path.join(platform.user_dir(prefer_local=True), "logs") os.makedirs(log_path, exist_ok=True) log_path = os.path.join(log_path, time.strftime("%Y%m%d-%H%M%S") + ".log") logs.add_file_output(log_path) if config_options.get('testnet'): Net.set_to(SVTestnet) elif config_options.get('scalingtestnet'): Net.set_to(SVScalingTestnet) # check uri uri = config_options.get('url') if uri: if not web.is_URI(uri): print('unknown command:', uri, file=sys.stderr) sys.exit(1) config_options['url'] = uri # todo: defer this to gui config = SimpleConfig(config_options) cmdname = config.get('cmd') # Set the app state proxy if cmdname == 'gui': try: from electrumsv.gui.qt.app_state import QtAppStateProxy except ImportError as e: platform.missing_import(e) QtAppStateProxy(config, 'qt') elif cmdname == 'daemon' and 'daemon_app_module' in config_options: load_app_module(config_options['daemon_app_module'], config) else: AppStateProxy(config, 'cmdline') app_state.set_app(DefaultApp()) # run non-RPC commands separately if cmdname in ['create', 'restore']: run_non_RPC(config) sys.exit(0) if cmdname == 'gui': fd, server = daemon.get_fd_or_server(config) if fd is not None: run_app_with_daemon(fd, True, config_options) else: result = server.gui(config_options) elif cmdname == 'daemon': subcommand = config.get('subcommand') if subcommand in ['load_wallet']: init_daemon(config_options) if subcommand in [None, 'start']: fd, server = daemon.get_fd_or_server(config) if fd is not None: if not app_state.has_app(): print("No application present to run.") sys.exit(0) if subcommand == 'start': if not hasattr(os, "fork"): print( f"Starting the daemon is not supported on {sys.platform}." ) sys.exit(0) pid = os.fork() if pid: print("Starting daemon (PID %d)" % pid, file=sys.stderr) sys.exit(0) run_app_with_daemon(fd, False, config_options) else: result = server.daemon(config_options) else: server = daemon.get_server(config) if server is not None: result = server.daemon(config_options) else: print("Daemon not running") sys.exit(1) else: # command line server = daemon.get_server(config) init_cmdline(config_options, server) if server is not None: result = server.run_cmdline(config_options) else: cmd = known_commands[cmdname] if cmd.requires_network: print("Daemon not running; try 'electrum-sv daemon start'") sys.exit(1) else: result = run_offline_command(config, config_options) # print result if isinstance(result, str): print(result) elif type(result) is dict and result.get('error'): print(result.get('error'), file=sys.stderr) elif result is not None: print(json_encode(result)) sys.exit(0)
def __init__(self): config = SimpleConfig() super().__init__(config, 'qt')
def __init__(self): config = SimpleConfig() super().__init__(config, 'qt') self.async_ = ASync()
def select_inputs_and_outputs( self, config: SimpleConfig, wallet: AbstractAccount, base_fee: int, split_count: int = 50, split_value: int = 10000, desired_utxo_count: int = 2000, max_utxo_margin: int = 200, require_confirmed: bool = True, ) -> Union[Tuple[List[UTXO], List[TxOutput], bool], Fault]: INPUT_COST = config.estimate_fee(INPUT_SIZE) OUTPUT_COST = config.estimate_fee(OUTPUT_SIZE) # adds extra inputs as required to meet the desired utxo_count. with wallet._utxos_lock: # Todo - optional filtering for frozen/maturity state (expensive for many utxos)? all_coins = wallet._utxos.values( ) # no filtering for frozen or maturity state for speed. # Ignore coins that are too expensive to send, or not confirmed. # Todo - this is inefficient to iterate over all coins (need better handling of dust utxos) if require_confirmed: get_metadata = wallet.get_transaction_metadata spendable_coins = [ coin for coin in all_coins if coin.value > (INPUT_COST + OUTPUT_COST) and get_metadata(coin.tx_hash).height > 0 ] else: spendable_coins = [ coin for coin in all_coins if coin.value > (INPUT_COST + OUTPUT_COST) ] inputs = [] outputs = [] selection_value = base_fee attempted_split = False if len(all_coins) < desired_utxo_count: attempted_split = True split_count = min( split_count, desired_utxo_count - len(all_coins) + max_utxo_margin) # Increase the transaction cost for the additional required outputs. selection_value += split_count * OUTPUT_COST + split_count * split_value # Collect sufficient inputs to cover the output value. # highest value coins first for splitting ordered_coins = sorted(spendable_coins, key=lambda k: k.value, reverse=True) for coin in ordered_coins: inputs.append(coin) if sum(input.value for input in inputs) >= selection_value: break # Increase the transaction cost for the additional required input. selection_value += INPUT_COST if len(inputs): # We ensure that we do not use conflicting addresses for the split outputs by # explicitly generating the addresses we are splitting to. fresh_keys = wallet.get_fresh_keys(RECEIVING_SUBPATH, count=split_count) for key in fresh_keys: derivation_path = wallet.get_derivation_path( key.keyinstance_id) pubkey = wallet.derive_pubkeys(derivation_path) outputs.append( TxOutput(split_value, pubkey.P2PKH_script())) return inputs, outputs, attempted_split for coin in spendable_coins: inputs.append(coin) if sum(input.value for input in inputs) >= selection_value: break # Increase the transaction cost for the additional required input. selection_value += INPUT_COST else: # We failed to collect enough inputs to cover the outputs. raise InsufficientCoinsError return inputs, outputs, attempted_split
#!/usr/bin/env python # A simple script that connects to a server and displays block headers import sys import time from electrumsv.simple_config import SimpleConfig from electrumsv.network import Network from electrumsv.util import json_encode # start network c = SimpleConfig() network = Network(c) network.start() # wait until connected while network.is_connecting(): time.sleep(0.1) if not network.is_connected(): print("daemon is not connected") sys.exit(1) # 2. send the subscription callback = lambda response: print(json_encode(response.get('result'))) network.send([('server.version', ["block_headers script", "1.2"])], callback) network.send([('blockchain.headers.subscribe', [])], callback) # 3. wait for results while network.is_connected():