Exemple #1
0
def coin(request):
    network = request.param
    Net.set_to(network)
    try:
        yield network.COIN
    finally:
        Net.set_to(SVMainnet)
def test_legacy_wallet_loading(storage_info: WalletStorageInfo) -> None:
    # When a wallet is composed of multiple files, we need to know which to load.
    wallet_filenames = []
    if storage_info.kind != StorageKind.DATABASE:
        wallet_filenames.append(storage_info.filename)
    if storage_info.kind in (StorageKind.DATABASE, StorageKind.HYBRID):
        wallet_filenames.append(storage_info.filename + DATABASE_EXT)

    temp_dir = tempfile.mkdtemp()
    for _wallet_filename in wallet_filenames:
        source_wallet_path = os.path.join(TEST_WALLET_PATH, _wallet_filename)
        wallet_path = os.path.join(temp_dir, _wallet_filename)
        shutil.copyfile(source_wallet_path, wallet_path)

    wallet_filename = storage_info.filename
    wallet_path = os.path.join(temp_dir, wallet_filename)

    if "testnet" in wallet_filename:
        Net.set_to(SVTestnet)

    password = "******"
    storage = WalletStorage(wallet_path)
    if "passworded" in wallet_filename:
        storage.decrypt(password)

    try:
        parent_wallet = ParentWallet(storage)
    except OSError as e:
        if "is not a valid Win32 application" not in e.args[1]:
            raise e
        pytest.xfail("Missing libusb for this architecture")
        return

    if "standard" in wallet_filename:
        is_bip39 = "bip39" in wallet_filename
        check_legacy_parent_of_standard_wallet(parent_wallet,
                                               is_bip39=is_bip39,
                                               password=password)
    elif "imported_privkey" in wallet_filename:
        check_legacy_parent_of_imported_privkey_wallet(parent_wallet)
    elif "imported_address" in wallet_filename:
        check_legacy_parent_of_imported_address_wallet(parent_wallet)
    elif "multisig" in wallet_filename:
        check_legacy_parent_of_multisig_wallet(parent_wallet)
    elif "hardware" in wallet_filename:
        check_legacy_parent_of_hardware_wallet(parent_wallet)
    else:
        raise Exception(f"unrecognised wallet file {wallet_filename}")

    if "testnet" in wallet_filename:
        Net.set_to(SVMainnet)
    def test_concurrent_tx_creation_and_broadcast(self, event_loop):
        n_txs = 10

        Net.set_to(SVRegTestnet)
        p2pkh_object = SVRegTestnet.REGTEST_FUNDS_PUBLIC_KEY.to_address()

        P2PKH_OUTPUT = {
            "value": 10000,
            "script_pubkey": p2pkh_object.to_script().to_hex()
        }

        payload = {
            "split_value": 20000,
            "split_count": 100,
            "password": "******"
        }

        url = f'http://127.0.0.1:9999/v1/regtest/dapp/wallets/' \
              f'{self.TEST_WALLET_NAME}/1/txs/split_utxos'

        # 1) split utxos sufficient for n transactions + confirm
        result = requests.post(url, json=payload)
        if result.status_code != 200:
            raise requests.exceptions.HTTPError(result.text)

        result2 = requests.post(
            f'http://127.0.0.1:9999/v1/regtest/dapp/wallets/'
            f'{self.TEST_WALLET_NAME}/1/generate_blocks')
        if result2.status_code != 200:
            raise requests.exceptions.HTTPError(result2.text)
        time.sleep(10)

        # 2) test concurrent transaction creation + broadcast
        payload2 = {"outputs": [P2PKH_OUTPUT], "password": "******"}

        async def main():
            async with aiohttp.ClientSession() as session:
                tasks = [
                    asyncio.create_task(self.create_and_send(
                        session, payload2)) for _ in range(0, n_txs)
                ]
                results = await asyncio.gather(*tasks, return_exceptions=True)

            for result in results:
                error_code = result.get('code')
                if error_code:
                    assert False, str(Fault(error_code, result.get('message')))

        event_loop.run_until_complete(main())
Exemple #4
0
def test_parse_uri_bip276() -> None:
    Net.set_to(SVTestnet)
    try:
        d = parse_URI(BIP276_URI)
    finally:
        Net.set_to(SVMainnet)

    expected_message = "the money i owe you"
    assert d == {
        "bip276": BIP276_TEXT,
        "script": BIP276_DATA,
        "amount": 12112121,
        "message": expected_message,
        "memo": expected_message,
    }
Exemple #5
0
def test_bip32_root():
    Net.set_to(SVMainnet)
    k = BIP32_KeyStore({})
    k.add_xprv_from_seed(b'BitcoinSV', 'm')
    assert k.xprv == ('xprv9s21ZrQH143K48ebsYkLU9UPzgdDVfhT6SMdWFJ8ZXak1bjKVRLu'
                      'xdmMCh7HZkwciZd7fga4gK4XW2QZhvWz5os6hJwqLfpZmW9r7pLgn9s')
    Net.set_to(SVTestnet)
    k = BIP32_KeyStore({})
    k.add_xprv_from_seed(b'BitcoinSV', 'm')
    assert k.xprv == ('tprv8ZgxMBicQKsPewt8Y7bqdo6PJp3RjBjTRzGkNfib3W5DoCUQUng'
                      'fUP8o7sGwa8Kw619tfnBpqfeKxsxJq8rvts8hDxA912YcgbuGZX3AZDd')
    Net.set_to(SVMainnet)
def network(request):
    network = request.param
    Net.set_to(network)
    yield network
    Net.set_to(SVMainnet)
Exemple #7
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)
Exemple #8
0
def test_legacy_wallet_loading(storage_info: WalletStorageInfo) -> None:
    # When a wallet is composed of multiple files, we need to know which to load.
    wallet_filenames = []
    if storage_info.kind != StorageKind.DATABASE:
        wallet_filenames.append(storage_info.filename)
    if storage_info.kind in (StorageKind.DATABASE, StorageKind.HYBRID):
        wallet_filenames.append(storage_info.filename + DATABASE_EXT)

    temp_dir = tempfile.mkdtemp()
    for _wallet_filename in wallet_filenames:
        source_wallet_path = os.path.join(TEST_WALLET_PATH, _wallet_filename)
        wallet_path = os.path.join(temp_dir, _wallet_filename)
        shutil.copyfile(source_wallet_path, wallet_path)

    wallet_filename = storage_info.filename
    wallet_path = os.path.join(temp_dir, wallet_filename)

    if "testnet" in wallet_filename:
        Net.set_to(SVTestnet)

    if storage_info.kind == StorageKind.HYBRID:
        pytest.xfail("old development database wallets not supported yet")

    password = None
    storage = WalletStorage(wallet_path)
    if "passworded" in wallet_filename:
        password = "******"
        text_store = storage.get_text_store()
        text_store.load_data(text_store.decrypt(password))
    if "encrypted" in wallet_filename:
        password = "******"
        check_no_password = False

    storage.upgrade(password is not None, password)

    try:
        wallet = Wallet(storage)
    except FileNotFoundError as e:
        if sys.version_info[:3] >= (3, 8, 0):
            msg = "Could not find module 'libusb-1.0.dll' (or one of its dependencies)."
            if msg in e.args[0]:
                pytest.xfail("libusb DLL could not be found")
                return
        raise e
    except OSError as e:
        if sys.version_info[:3] < (3, 8, 0):
            if "The specified module could not be found" in e.args[1]:
                pytest.xfail("libusb DLL could not be found")
                return
        raise e

    old_password = password
    password = "******"
    wallet.update_password(password, old_password)

    if "standard" in wallet_filename:
        is_bip39 = "bip39" in wallet_filename
        check_legacy_parent_of_standard_wallet(wallet, is_bip39=is_bip39,
            password=password)
    elif "imported_privkey" in wallet_filename:
        check_legacy_parent_of_imported_privkey_wallet(wallet)
    elif "imported_address" in wallet_filename:
        check_legacy_parent_of_imported_address_wallet(wallet)
    elif "multisig" in wallet_filename:
        check_legacy_parent_of_multisig_wallet(wallet)
    elif "hardware" in wallet_filename:
        check_legacy_parent_of_hardware_wallet(wallet)
    else:
        raise Exception(f"unrecognised wallet file {wallet_filename}")

    if "testnet" in wallet_filename:
        Net.set_to(SVMainnet)
Exemple #9
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)
Exemple #10
0
 def tearDownClass(cls):
     super().tearDownClass()
     Net.set_to(SVMainnet)
Exemple #11
0
 def setUpClass(cls):
     super().setUpClass()
     Net.set_to(SVTestnet)
Exemple #12
0
    def setUp(self) -> None:
        Net.set_to(SVMainnet)

        self.storage = MockStorage()
        self.wallet = _Wallet(self.storage)
    def test_create_and_broadcast_exception_handling(self, event_loop):
        Net.set_to(SVRegTestnet)
        p2pkh_object = SVRegTestnet.REGTEST_FUNDS_PUBLIC_KEY.to_address()

        async def main():
            async with aiohttp.ClientSession() as session:
                # Todo - use websocket instead of sleeps
                time.sleep(6)
                # get tx history before tests to compare later
                result1 = await self.get_tx_history(session)
                error_code = result1.get('code')
                if error_code:
                    assert False, result1
                len_tx_hist_before = len(result1['history'])

                # get utxos
                result2 = await self.get_utxos(session)
                error_code = result2.get('code')
                if error_code:
                    assert False, result2
                utxos = result2['utxos']

                # Prepare for two txs that use the same utxo
                P2PKH_OUTPUT = {
                    "value": 100,
                    "script_pubkey": p2pkh_object.to_script().to_hex()
                }
                # base tx
                payload1 = {
                    "outputs": [P2PKH_OUTPUT],
                    "password": "******",
                    "utxos": [utxos[0]]
                }
                # trigger mempool conflict
                payload2 = {
                    "outputs": [P2PKH_OUTPUT, P2PKH_OUTPUT],
                    "password": "******",
                    "utxos": [utxos[0]]
                }
                # trigger 'duplicate set' internal server error (same exact txid in tx cache)
                payload3 = {
                    "outputs": [P2PKH_OUTPUT],
                    "password": "******",
                    "utxos": [utxos[0]]
                }
                # First tx
                result3 = await self.create_and_send(session, payload1)
                error_code = result3.get('code')
                if error_code:
                    assert False, result3

                # Trigger "mempool conflict"
                result4 = await self.create_and_send(session, payload2)
                error_code = result4.get('code')
                if not error_code:
                    assert False, result4

                assert result4['code'] == 40011

                # Trigger 'duplicate set' internal server error (same exact txid in tx cache)
                result4 = await self.create_and_send(session, payload3)
                error_code = result4.get('code')
                if not error_code:
                    assert False, result4

                assert result4['code'] == 50000

                # trigger insufficient coins
                P2PKH_OUTPUT = {
                    "value": 1_000 * 100_000_000,
                    "script_pubkey": p2pkh_object.to_script().to_hex()
                }
                payload2 = {"outputs": [P2PKH_OUTPUT], "password": "******"}
                result5 = await self.create_and_send(session, payload2)
                error_code = result5.get('code')
                if not error_code:
                    assert False, result5
                assert result5 == {
                    'code': 40006,
                    'message':
                    'You have insufficient coins for this transaction'
                }

                # Todo - use websocket instead of sleeps
                time.sleep(6)

                # check that only 1 new txs was created
                result6 = await self.get_tx_history(session)
                error_code = result6.get('code')
                if error_code:
                    assert False, result6
                len_tx_hist_after = len(result6['history'])

                # only one extra tx should exist (in the other cases, no tx should exist)
                assert len_tx_hist_before == (len_tx_hist_after - 1)

        event_loop.run_until_complete(main())