Exemplo n.º 1
0
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
Exemplo n.º 2
0
 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"))
Exemplo n.º 3
0
 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"))
Exemplo n.º 4
0
 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"))
Exemplo n.º 5
0
 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"))
Exemplo n.º 6
0
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
Exemplo n.º 7
0
 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)
Exemplo n.º 8
0
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
Exemplo n.º 9
0
 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)
Exemplo n.º 10
0
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
Exemplo n.º 11
0
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)
Exemplo n.º 12
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)
Exemplo n.º 13
0
 def __init__(self):
     config = SimpleConfig()
     super().__init__(config, 'qt')
Exemplo n.º 14
0
 def __init__(self):
     config = SimpleConfig()
     super().__init__(config, 'qt')
     self.async_ = ASync()
Exemplo n.º 15
0
    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
Exemplo n.º 16
0
#!/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():