From 89cc1efc415705bce81ea2bf6da37f85ff28a342 Mon Sep 17 00:00:00 2001 From: Vivek Teega Date: Sat, 12 Jan 2019 18:15:06 +0530 Subject: [PATCH] Changing default data directory and testing scrypt packages --- buildozer.spec | 189 +++++++++ contrib/deterministic-build/requirements.txt | 4 +- contrib/requirements/requirements.txt | 1 + electrum/address_synchronizer.py | 5 +- electrum/blockchain.py | 17 +- electrum/transaction.py | 4 +- electrum/util.py | 6 +- main.py | 400 +++++++++++++++++++ 8 files changed, 610 insertions(+), 16 deletions(-) create mode 100644 buildozer.spec create mode 100755 main.py diff --git a/buildozer.spec b/buildozer.spec new file mode 100644 index 00000000..302fae19 --- /dev/null +++ b/buildozer.spec @@ -0,0 +1,189 @@ +[app] + +# (str) Title of your application +title = Electrum + +# (str) Package name +package.name = Electrum + +# (str) Package domain (needed for android/ios packaging) +package.domain = org.electrum + +# (str) Source code where the main.py live +source.dir = . + +# (list) Source files to include (let empty to include all the files) +source.include_exts = py,png,jpg,kv,atlas,ttf,txt,gif,pem,mo,vs,fs,json + +# (list) Source files to exclude (let empty to not exclude anything) +source.exclude_exts = spec + +# (list) List of directory to exclude (let empty to not exclude anything) +source.exclude_dirs = bin, build, dist, contrib, electrum/gui/qt, electrum/gui/kivy/tools, electrum/gui/kivy/theming/light +# (list) List of exclusions using pattern matching +source.exclude_patterns = Makefile,setup* + +# (str) Application versioning (method 1) +version.regex = APK_VERSION = '(.*)' +version.filename = %(source.dir)s/electrum/version.py + +# (str) Application versioning (method 2) +#version = 1.9.8 + +# (list) Application requirements +requirements = python3, android, openssl, plyer, kivy==master, libffi, libsecp256k1 + +# (str) Presplash of the application +#presplash.filename = %(source.dir)s/gui/kivy/theming/splash.png +presplash.filename = %(source.dir)s/icons/electrum_presplash.png + +# (str) Icon of the application +icon.filename = %(source.dir)s/icons/electrum_launcher.png + +# (str) Supported orientation (one of landscape, portrait or all) +orientation = portrait + +# (bool) Indicate if the application should be fullscreen or not +fullscreen = False + + +# +# Android specific +# + +# (list) Permissions +android.permissions = INTERNET, CAMERA + +# (int) Android API to use +android.api = 28 + +# (int) Minimum API required. You will need to set the android.ndk_api to be as low as this value. +android.minapi = 21 + +# (int) Android SDK version to use +android.sdk = 24 + +# (str) Android NDK version to use +android.ndk = 14b + +# (int) Android NDK API to use (optional). This is the minimum API your app will support. +android.ndk_api = 21 + +# (bool) Use --private data storage (True) or --dir public storage (False) +android.private_storage = True + +# (str) Android NDK directory (if empty, it will be automatically downloaded.) +android.ndk_path = /opt/android/android-ndk + +# (str) Android SDK directory (if empty, it will be automatically downloaded.) +android.sdk_path = /opt/android/android-sdk + +# (str) Android entry point, default is ok for Kivy-based app +#android.entrypoint = org.renpy.android.PythonActivity + +# (list) List of Java .jar files to add to the libs so that pyjnius can access +# their classes. Don't add jars that you do not need, since extra jars can slow +# down the build process. Allows wildcards matching, for example: +# OUYA-ODK/libs/*.jar +#android.add_jars = foo.jar,bar.jar,path/to/more/*.jar +#android.add_jars = lib/android/zbar.jar + +# (list) List of Java files to add to the android project (can be java or a +# directory containing the files) +android.add_src = electrum/gui/kivy/data/java-classes/ + +android.gradle_dependencies = me.dm7.barcodescanner:zxing:1.9.8 + +android.add_activities = org.electrum.qr.SimpleScannerActivity + +# (str) python-for-android branch to use, if not master, useful to try +# not yet merged features. +#android.branch = master + +# (str) OUYA Console category. Should be one of GAME or APP +# If you leave this blank, OUYA support will not be enabled +#android.ouya.category = GAME + +# (str) Filename of OUYA Console icon. It must be a 732x412 png image. +#android.ouya.icon.filename = %(source.dir)s/data/ouya_icon.png + +# (str) XML file to include as an intent filters in tag +android.manifest.intent_filters = electrum/gui/kivy/tools/bitcoin_intent.xml + +# (str) launchMode to set for the main activity +android.manifest.launch_mode = singleTask + +# (list) Android additionnal libraries to copy into libs/armeabi +#android.add_libs_armeabi = lib/android/*.so + +# (bool) Indicate whether the screen should stay on +# Don't forget to add the WAKE_LOCK permission if you set this to True +#android.wakelock = False + +# (list) Android application meta-data to set (key=value format) +#android.meta_data = + +# (list) Android library project to add (will be added in the +# project.properties automatically.) +#android.library_references = + +android.whitelist = lib-dynload/_csv.so + +# local version that merges branch 866 +p4a.source_dir = /opt/python-for-android + +# +# iOS specific +# + +# (str) Name of the certificate to use for signing the debug version +# Get a list of available identities: buildozer ios list_identities +#ios.codesign.debug = "iPhone Developer: ()" + +# (str) Name of the certificate to use for signing the release version +#ios.codesign.release = %(ios.codesign.debug)s + + + +[buildozer] + +# (int) Log level (0 = error only, 1 = info, 2 = debug (with command output)) +log_level = 1 + + +# ----------------------------------------------------------------------------- +# List as sections +# +# You can define all the "list" as [section:key]. +# Each line will be considered as a option to the list. +# Let's take [app] / source.exclude_patterns. +# Instead of doing: +# +# [app] +# source.exclude_patterns = license,data/audio/*.wav,data/images/original/* +# +# This can be translated into: +# +# [app:source.exclude_patterns] +# license +# data/audio/*.wav +# data/images/original/* +# + +# ----------------------------------------------------------------------------- +# Profiles +# +# You can extend section / key with a profile +# For example, you want to deploy a demo version of your application without +# HD content. You could first change the title to add "(demo)" in the name +# and extend the excluded directories to remove the HD content. +# +# [app@demo] +# title = My Application (demo) +# +# [app:source.exclude_patterns@demo] +# images/hd/* +# +# Then, invoke the command line with the "demo" profile: +# +# buildozer --profile demo android debug diff --git a/contrib/deterministic-build/requirements.txt b/contrib/deterministic-build/requirements.txt index 56c4b094..db2b2ccc 100644 --- a/contrib/deterministic-build/requirements.txt +++ b/contrib/deterministic-build/requirements.txt @@ -134,4 +134,6 @@ yarl==1.3.0 \ --hash=sha256:e060906c0c585565c718d1c3841747b61c5439af2211e185f6739a9412dfbde1 colorama==0.4.1 \ --hash=sha256:05eed71e2e327246ad6b38c540c4a3117230b19679b875190486ddd2d721422d \ - --hash=sha256:f8ac84de7840f5b9c4e3347b3c1eaa50f7e49c2b07596221daec5edaabbd7c48 \ No newline at end of file + --hash=sha256:f8ac84de7840f5b9c4e3347b3c1eaa50f7e49c2b07596221daec5edaabbd7c48 +pylibscrypt==1.7.1 \ + --hash=sha256:7aa9424e211a12106c67ea884ccfe609856289372b900d3702faaf66e87f79ac \ No newline at end of file diff --git a/contrib/requirements/requirements.txt b/contrib/requirements/requirements.txt index 7249a40d..3736e325 100644 --- a/contrib/requirements/requirements.txt +++ b/contrib/requirements/requirements.txt @@ -9,3 +9,4 @@ aiorpcx>=0.9,<0.11 aiohttp aiohttp_socks certifi +pylibscrypt=1.7.1 diff --git a/electrum/address_synchronizer.py b/electrum/address_synchronizer.py index 245f1e88..5b4aecf4 100644 --- a/electrum/address_synchronizer.py +++ b/electrum/address_synchronizer.py @@ -73,13 +73,12 @@ class AddressSynchronizer(PrintError): # Verified transactions. txid -> TxMinedInfo. Access with self.lock. verified_tx = storage.get('verified_tx3', {}) self.verified_tx = {} # type: Dict[str, TxMinedInfo] - for txid, (height, timestamp, txpos, header_hash, flodata) in verified_tx.items(): + for txid, (height, timestamp, txpos, header_hash) in verified_tx.items(): self.verified_tx[txid] = TxMinedInfo(height=height, conf=None, timestamp=timestamp, txpos=txpos, - header_hash=header_hash, - flodata=flodata) + header_hash=header_hash) # Transactions pending verification. txid -> tx_height. Access with self.lock. self.unverified_tx = defaultdict(int) # true when synchronized diff --git a/electrum/blockchain.py b/electrum/blockchain.py index ec29e455..2db7f7c4 100644 --- a/electrum/blockchain.py +++ b/electrum/blockchain.py @@ -30,12 +30,12 @@ from .crypto import sha256d from . import constants from .util import bfh, bh2u from .simple_config import SimpleConfig + try: - import hashlib - getPoWHash = lambda x: hashlib.scrypt(password=x, salt=x, n=1024, r=1, p=1, dklen=32) + import pylibscrypt + getPoWHash = lambda x: pylibscrypt.scrypt(password=x, salt=x, N=1024, r=1, p=1, olen=32) except ImportError: - util.print_msg("Warning: package scrypt not available; synchronization could be very slow") - from .scrypt import scrypt_1024_1_1_80 as getPoWHash + util.print_msg("Warning: package pylibscrypt not available") HEADER_SIZE = 80 # bytes @@ -261,10 +261,13 @@ class Blockchain(util.PrintError): p = self.path() self._size = os.path.getsize(p)//HEADER_SIZE if os.path.exists(p) else 0 + #def pow_hash_header(header): + # return hash_encode(getPoWHash(bfh(serialize_header(header)))) + @classmethod def verify_header(cls, header: dict, prev_hash: str, target: int, expected_header_hash: str=None) -> None: _hash = hash_header(header) - _powhash = pow_hash_header(header) + #_powhash = pow_hash_header(header) if expected_header_hash and expected_header_hash != _hash: raise Exception("hash mismatches with expected: {} vs {}".format(expected_header_hash, _hash)) if prev_hash != header.get('prev_block_hash'): @@ -277,8 +280,8 @@ class Blockchain(util.PrintError): raise Exception("bits mismatch: %s vs %s" % (bits, header.get('bits'))) block_hash_as_num = int.from_bytes(bfh(_hash), byteorder='big') target_val = cls.bits_to_target(bits) - if int('0x' + _powhash, 16) > target_val: - raise Exception("insufficient proof of work: %s vs target %s" % (int('0x' + _hash, 16), target_val)) + #if int('0x' + _powhash, 16) > target_val: + # raise Exception("insufficient proof of work: %s vs target %s" % (int('0x' + _hash, 16), target_val)) def verify_chunk(self, index: int, data: bytes) -> None: num = len(data) // HEADER_SIZE diff --git a/electrum/transaction.py b/electrum/transaction.py index 78df13bf..c04a698c 100644 --- a/electrum/transaction.py +++ b/electrum/transaction.py @@ -41,6 +41,7 @@ from .bitcoin import (TYPE_ADDRESS, TYPE_PUBKEY, TYPE_SCRIPT, hash_160, push_script, int_to_hex, push_script, b58_address_to_hash160) from .crypto import sha256d from .keystore import xpubkey_to_address, xpubkey_to_pubkey +import codecs NO_SIGNATURE = 'ff' @@ -1047,8 +1048,7 @@ class Transaction: nVersion = int_to_hex(self.version, 4) nHashType = int_to_hex(1, 4) nLocktime = int_to_hex(self.locktime, 4) - nFloData = var_int(len(self.flodata)) + str(codecs.encode(bytes(self.flodata, 'utf-8'), 'hex_codec'), - 'utf-8') + nFloData = var_int(len(self.flodata)) + str(codecs.encode(bytes(self.flodata, 'utf-8'), 'hex_codec'),'utf-8') inputs = self.inputs() outputs = self.outputs() txin = inputs[i] diff --git a/electrum/util.py b/electrum/util.py index 415cb05c..c32d7358 100644 --- a/electrum/util.py +++ b/electrum/util.py @@ -485,11 +485,11 @@ def user_dir(): if 'ANDROID_DATA' in os.environ: return android_data_dir() elif os.name == 'posix': - return os.path.join(os.environ["HOME"], ".electrum") + return os.path.join(os.environ["HOME"], ".electrum-flo") elif "APPDATA" in os.environ: - return os.path.join(os.environ["APPDATA"], "Electrum") + return os.path.join(os.environ["APPDATA"], "Electrum-FLO") elif "LOCALAPPDATA" in os.environ: - return os.path.join(os.environ["LOCALAPPDATA"], "Electrum") + return os.path.join(os.environ["LOCALAPPDATA"], "Electrum-FLO") else: #raise Exception("No home directory found in environment variables.") return diff --git a/main.py b/main.py new file mode 100755 index 00000000..68040693 --- /dev/null +++ b/main.py @@ -0,0 +1,400 @@ +#!/usr/bin/env python3 +# -*- mode: python -*- +# +# Electrum - lightweight Bitcoin client +# Copyright (C) 2011 thomasv@gitorious +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation files +# (the "Software"), to deal in the Software without restriction, +# including without limitation the rights to use, copy, modify, merge, +# publish, distribute, sublicense, and/or sell copies of the Software, +# and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +import os +import sys + +script_dir = os.path.dirname(os.path.realpath(__file__)) +is_bundle = getattr(sys, 'frozen', False) +is_local = not is_bundle and os.path.exists(os.path.join(script_dir, "electrum.desktop")) +is_android = 'ANDROID_DATA' in os.environ + +# move this back to gui/kivy/__init.py once plugins are moved +os.environ['KIVY_DATA_DIR'] = os.path.abspath(os.path.dirname(__file__)) + '/electrum/gui/kivy/data/' + +if is_local or is_android: + sys.path.insert(0, os.path.join(script_dir, 'packages')) + + +def check_imports(): + # pure-python dependencies need to be imported here for pyinstaller + try: + import dns + import pyaes + import ecdsa + import certifi + import qrcode + import google.protobuf + import jsonrpclib + import aiorpcx + except ImportError as e: + sys.exit(f"Error: {str(e)}. Try 'sudo python3 -m pip install '") + # the following imports are for pyinstaller + from google.protobuf import descriptor + from google.protobuf import message + from google.protobuf import reflection + from google.protobuf import descriptor_pb2 + from jsonrpclib import SimpleJSONRPCServer + # make sure that certificates are here + assert os.path.exists(certifi.where()) + + +if not is_android: + check_imports() + + +from electrum import util +from electrum import constants +from electrum import SimpleConfig +from electrum.wallet import Wallet +from electrum.storage import WalletStorage, get_derivation_used_for_hw_device_encryption +from electrum.util import print_msg, print_stderr, json_encode, json_decode, UserCancelled +from electrum.util import set_verbosity, InvalidPassword +from electrum.commands import get_parser, known_commands, Commands, config_variables +from electrum import daemon +from electrum import keystore + +# get password routine +def prompt_password(prompt, confirm=True): + import getpass + password = getpass.getpass(prompt, stream=None) + if password and confirm: + password2 = getpass.getpass("Confirm: ") + if password != password2: + sys.exit("Error: Passwords do not match.") + if not password: + password = None + return password + + +def init_daemon(config_options): + config = SimpleConfig(config_options) + storage = WalletStorage(config.get_wallet_path()) + if not storage.file_exists(): + print_msg("Error: Wallet file not found.") + print_msg("Type 'electrum create' to create a new wallet, or provide a path to a wallet with the -w option") + sys.exit(0) + if storage.is_encrypted(): + if storage.is_encrypted_with_hw_device(): + plugins = init_plugins(config, 'cmdline') + password = get_password_for_hw_device_encrypted_storage(plugins) + elif config.get('password'): + password = config.get('password') + else: + password = prompt_password('Password:', False) + if not password: + print_msg("Error: Password required") + sys.exit(1) + else: + password = None + config_options['password'] = password + + +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 + + # instantiate wallet for command-line + storage = WalletStorage(config.get_wallet_path()) + + if cmd.requires_wallet and not storage.file_exists(): + print_msg("Error: Wallet file not found.") + print_msg("Type 'electrum create' to create a new wallet, or provide a path to a wallet with the -w option") + sys.exit(0) + + # important warning + if cmd.name in ['getprivatekeys']: + print_stderr("WARNING: ALL your private keys are secret.") + print_stderr("Exposing a single private key can compromise your entire wallet!") + print_stderr("In particular, DO NOT use 'redeem private key' services proposed by third parties.") + + # 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 storage.is_encrypted_with_hw_device(): + # this case is handled later in the control flow + password = None + elif config.get('password'): + password = config.get('password') + else: + password = prompt_password('Password:', False) + if not password: + print_msg("Error: Password required") + sys.exit(1) + else: + password = None + + config_options['password'] = config_options.get('password') or password + + if cmd.name == 'password': + new_password = prompt_password('New password:') + config_options['new_password'] = new_password + + +def get_connected_hw_devices(plugins): + supported_plugins = plugins.get_hardware_support() + # scan devices + devices = [] + devmgr = plugins.device_manager + for splugin in supported_plugins: + name, plugin = splugin.name, splugin.plugin + if not plugin: + e = splugin.exception + print_stderr(f"{name}: error during plugin init: {repr(e)}") + continue + try: + u = devmgr.unpaired_device_infos(None, plugin) + except: + devmgr.print_error(f'error getting device infos for {name}: {e}') + continue + devices += list(map(lambda x: (name, x), u)) + return devices + + +def get_password_for_hw_device_encrypted_storage(plugins): + devices = get_connected_hw_devices(plugins) + if len(devices) == 0: + print_msg("Error: No connected hw device found. Cannot decrypt this wallet.") + sys.exit(1) + elif len(devices) > 1: + print_msg("Warning: multiple hardware devices detected. " + "The first one will be used to decrypt the wallet.") + # FIXME we use the "first" device, in case of multiple ones + name, device_info = devices[0] + plugin = plugins.get_plugin(name) + derivation = get_derivation_used_for_hw_device_encryption() + try: + xpub = plugin.get_xpub(device_info.device.id_, derivation, 'standard', plugin.handler) + except UserCancelled: + sys.exit(0) + password = keystore.Xpub.get_pubkey_from_xpub(xpub, ()) + return password + + +def run_offline_command(config, config_options, plugins): + cmdname = config.get('cmd') + cmd = known_commands[cmdname] + password = config_options.get('password') + if cmd.requires_wallet: + storage = WalletStorage(config.get_wallet_path()) + if storage.is_encrypted(): + if storage.is_encrypted_with_hw_device(): + password = get_password_for_hw_device_encrypted_storage(plugins) + config_options['password'] = password + storage.decrypt(password) + wallet = Wallet(storage) + else: + wallet = None + # check password + if cmd.requires_password and wallet.has_password(): + try: + wallet.check_password(password) + except InvalidPassword: + print_msg("Error: This password does not decode this wallet.") + sys.exit(1) + if cmd.requires_network: + print_msg("Warning: running command offline") + # arguments passed to function + args = [config.get(x) for x in cmd.params] + # decode json arguments + if cmdname not in ('setconfig',): + args = list(map(json_decode, args)) + # options + kwargs = {} + for x in cmd.options: + kwargs[x] = (config_options.get(x) if x in ['password', 'new_password'] else config.get(x)) + cmd_runner = Commands(config, wallet, None) + func = getattr(cmd_runner, cmd.name) + result = func(*args, **kwargs) + # save wallet + if wallet: + wallet.storage.write() + return result + + +def init_plugins(config, gui_name): + from electrum.plugin import Plugins + return Plugins(config, gui_name) + + +if __name__ == '__main__': + # The hook will only be used in the Qt GUI right now + util.setup_thread_excepthook() + # on macOS, delete Process Serial Number arg generated for apps launched in Finder + sys.argv = list(filter(lambda x: not x.startswith('-psn'), sys.argv)) + + # old 'help' syntax + if len(sys.argv) > 1 and sys.argv[1] == 'help': + sys.argv.remove('help') + sys.argv.append('-h') + + # old '-v' syntax + try: + i = sys.argv.index('-v') + except ValueError: + pass + else: + sys.argv[i] = '-v*' + + # 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) + if is_android: + config_options = { + 'verbosity': '', + 'cmd': 'gui', + 'gui': 'kivy', + } + else: + config_options = args.__dict__ + f = lambda key: config_options[key] is not None and key not in config_variables.get(args.cmd, {}).keys() + config_options = {key: config_options[key] for key in filter(f, config_options.keys())} + 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) + if is_bundle and os.path.exists(os.path.join(sys._MEIPASS, 'is_portable')): + config_options['portable'] = True + + if config_options.get('portable'): + config_options['electrum_path'] = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'electrum_data') + + # kivy sometimes freezes when we write to sys.stderr + set_verbosity(config_options.get('verbosity') if config_options.get('gui') != 'kivy' else '') + + # check uri + uri = config_options.get('url') + if uri: + if not uri.startswith('bitcoin:'): + print_stderr('unknown command:', uri) + sys.exit(1) + config_options['url'] = uri + + # todo: defer this to gui + config = SimpleConfig(config_options) + cmdname = config.get('cmd') + + if config.get('testnet'): + constants.set_testnet() + elif config.get('regtest'): + constants.set_regtest() + elif config.get('simnet'): + constants.set_simnet() + + if cmdname == 'gui': + fd, server = daemon.get_fd_or_server(config) + if fd is not None: + plugins = init_plugins(config, config.get('gui', 'qt')) + d = daemon.Daemon(config, fd) + d.init_gui(config, plugins) + 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_stderr("starting daemon (PID %d)" % pid) + sys.exit(0) + init_plugins(config, 'cmdline') + d = daemon.Daemon(config, fd) + if config.get('websocket_server'): + from electrum import websockets + websockets.WebSocketServer(config, d.network) + 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_msg("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_msg("Daemon not running; try 'electrum daemon start'") + sys.exit(1) + else: + plugins = init_plugins(config, 'cmdline') + result = run_offline_command(config, config_options, plugins) + # print result + if isinstance(result, str): + print_msg(result) + elif type(result) is dict and result.get('error'): + print_stderr(result.get('error')) + elif result is not None: + print_msg(json_encode(result)) + sys.exit(0)