Exemplo n.º 1
0
    def request_felix_landing_page(_result):

        # Init an equal Felix to the already running one.
        felix_config = FelixConfiguration.from_configuration_file(
            filepath=configuration_file_location,
            registry_filepath=MOCK_REGISTRY_FILEPATH)

        felix_config.attach_keyring()
        felix_config.keyring.unlock(password=INSECURE_DEVELOPMENT_PASSWORD)
        felix = felix_config.produce()

        # Make a flask app
        web_app = felix.make_web_app()
        test_client = web_app.test_client()

        # Load the landing page
        response = test_client.get('/')
        assert response.status_code == 200

        # Register a new recipient
        response = test_client.post(
            '/register', data={'address': testerchain.client.accounts[-1]})
        assert response.status_code == 200

        return
Exemplo n.º 2
0
def _get_config(emitter, network, config_file, registry_filepath, eth_node,
                provider_uri, host, port, db_filepath, poa):
    # Domains -> bytes | or default
    domains = [network] if network else None

    # Load Felix from Configuration File with overrides
    try:
        felix_config = FelixConfiguration.from_configuration_file(
            filepath=config_file,
            domains=domains,
            registry_filepath=registry_filepath,
            provider_process=eth_node,
            provider_uri=provider_uri,
            rest_host=host,
            rest_port=port,
            db_filepath=db_filepath,
            poa=poa)

        return felix_config
    except FileNotFoundError:
        emitter.echo(
            f"No Felix configuration file found at {config_file}. "
            f"Check the filepath or run 'nucypher felix init' to create a new system configuration."
        )
        raise click.Abort
Exemplo n.º 3
0
def init(
        click_config,

        # Admin Options
        checksum_address,
        geth,
        dev,
        network,
        registry_filepath,
        provider_uri,
        host,
        db_filepath,
        poa,

        # Other
        config_root,
        discovery_port):
    """
    Create a brand-new Felix.
    """
    emitter = _setup_emitter(click_config, checksum_address)

    ETH_NODE = NO_BLOCKCHAIN_CONNECTION
    if geth:
        ETH_NODE = actions.get_provider_process(dev)
        provider_uri = ETH_NODE.provider_uri

    if not config_root:  # Flag
        config_root = DEFAULT_CONFIG_ROOT  # Envvar or init-only default

    try:
        new_felix_config = FelixConfiguration.generate(
            password=get_nucypher_password(confirm=True),
            config_root=config_root,
            rest_host=host,
            rest_port=discovery_port,
            db_filepath=db_filepath,
            domains={network} if network else None,
            checksum_address=checksum_address,
            registry_filepath=registry_filepath,
            provider_uri=provider_uri,
            provider_process=ETH_NODE,
            poa=poa)
    except Exception as e:
        if click_config.debug:
            raise
        else:
            emitter.echo(str(e), color='red', bold=True)
            raise click.Abort

    # Paint Help
    painting.paint_new_installation_help(emitter,
                                         new_configuration=new_felix_config)
Exemplo n.º 4
0
 def generate_config(self, config_root, discovery_port):
     return FelixConfiguration.generate(
         password=get_nucypher_password(confirm=True),
         config_root=config_root,
         rest_host=self.host,
         rest_port=discovery_port,
         db_filepath=self.db_filepath,
         domains=self.domains,
         checksum_address=self.checksum_address,
         registry_filepath=self.registry_filepath,
         provider_uri=self.provider_uri,
         provider_process=self.eth_node,
         poa=self.poa)
Exemplo n.º 5
0
 def generate_config(self, config_root, discovery_port):
     return FelixConfiguration.generate(
         password=get_nucypher_password(emitter=StdoutEmitter(), confirm=True),
         config_root=config_root,
         rest_host=self.host,
         rest_port=discovery_port,
         db_filepath=self.db_filepath,
         domain=self.domain,
         checksum_address=self.checksum_address,
         registry_filepath=self.registry_filepath,
         provider_uri=self.provider_uri,
         signer_uri=self.signer_uri,
         poa=self.poa)
Exemplo n.º 6
0
 def create_config(self, emitter, config_file):
     # Load Felix from Configuration File with overrides
     try:
         return FelixConfiguration.from_configuration_file(
             emitter=emitter,
             filepath=config_file,
             domains=self.domains,
             registry_filepath=self.registry_filepath,
             provider_process=self.eth_node,
             provider_uri=self.provider_uri,
             rest_host=self.host,
             rest_port=self.port,
             db_filepath=self.db_filepath,
             poa=self.poa)
     except FileNotFoundError:
         return actions.handle_missing_configuration_file(
             character_config_class=FelixConfiguration,
             config_file=config_file)
Exemplo n.º 7
0
 def create_config(self, emitter, config_file):
     # Load Felix from Configuration File with overrides
     if not config_file:
         config_file = select_config_file(emitter=emitter,
                                          checksum_address=self.checksum_address,
                                          config_class=FelixConfiguration)
     try:
         return FelixConfiguration.from_configuration_file(
             emitter=emitter,
             filepath=config_file,
             domain=self.domain,
             registry_filepath=self.registry_filepath,
             provider_uri=self.provider_uri,
             signer=self.signer_uri,
             rest_host=self.host,
             rest_port=self.port,
             db_filepath=self.db_filepath,
             poa=self.poa)
     except FileNotFoundError:
         return handle_missing_configuration_file(
             character_config_class=FelixConfiguration,
             config_file=config_file
         )
Exemplo n.º 8
0
def test_run_felix(click_runner, testerchain, test_registry, agency,
                   deploy_user_input):

    clock = Clock()
    Felix._CLOCK = clock
    Felix.DISTRIBUTION_INTERVAL = 5  # seconds
    Felix.DISBURSEMENT_INTERVAL = 0.01  # hours
    Felix.STAGING_DELAY = 2  # seconds

    # Main thread (Flask)
    os.environ['NUCYPHER_FELIX_DB_SECRET'] = INSECURE_DEVELOPMENT_PASSWORD

    # Test subproc (Click)
    envvars = {
        NUCYPHER_ENVVAR_KEYRING_PASSWORD: INSECURE_DEVELOPMENT_PASSWORD,
        'NUCYPHER_FELIX_DB_SECRET': INSECURE_DEVELOPMENT_PASSWORD,
        'FLASK_DEBUG': '1'
    }

    # Felix creates a system configuration
    init_args = ('felix', 'init', '--debug', '--registry-filepath',
                 MOCK_REGISTRY_FILEPATH, '--checksum-address',
                 testerchain.client.accounts[0], '--config-root',
                 MOCK_CUSTOM_INSTALLATION_PATH_2, '--network',
                 TEMPORARY_DOMAIN, '--provider', TEST_PROVIDER_URI)

    result = click_runner.invoke(nucypher_cli,
                                 init_args,
                                 catch_exceptions=False,
                                 env=envvars)
    assert result.exit_code == 0

    configuration_file_location = os.path.join(
        MOCK_CUSTOM_INSTALLATION_PATH_2,
        FelixConfiguration.generate_filename())

    # Felix Creates a Database
    db_args = ('felix', 'createdb', '--debug', '--config-file',
               configuration_file_location, '--provider', TEST_PROVIDER_URI)

    result = click_runner.invoke(nucypher_cli,
                                 db_args,
                                 catch_exceptions=False,
                                 env=envvars)
    assert result.exit_code == 0

    # Felix Runs Web Services
    def run_felix():
        args = ('felix', 'run', '--debug', '--config-file',
                configuration_file_location, '--provider', TEST_PROVIDER_URI,
                '--dry-run')

        run_result = click_runner.invoke(nucypher_cli,
                                         args,
                                         catch_exceptions=False,
                                         env=envvars)
        assert run_result.exit_code == 0
        return run_result

    # A (mocked) client requests Felix's services
    def request_felix_landing_page(_result):

        # Init an equal Felix to the already running one.
        felix_config = FelixConfiguration.from_configuration_file(
            filepath=configuration_file_location,
            registry_filepath=MOCK_REGISTRY_FILEPATH)

        felix_config.attach_keyring()
        felix_config.keyring.unlock(password=INSECURE_DEVELOPMENT_PASSWORD)
        felix = felix_config.produce()

        # Make a flask app
        web_app = felix.make_web_app()
        test_client = web_app.test_client()

        # Register a new recipient
        response = test_client.post(
            '/register', data={'address': testerchain.client.accounts[-1]})
        assert response.status_code == 200

        return

    def time_travel(_result):
        clock.advance(amount=60)

    # Record starting ether balance
    recipient = testerchain.client.accounts[-1]
    staker = Staker(checksum_address=recipient,
                    registry=test_registry,
                    is_me=True)
    original_eth_balance = staker.eth_balance

    # Run the callbacks
    d = threads.deferToThread(run_felix)
    d.addCallback(request_felix_landing_page)
    d.addCallback(time_travel)

    yield d

    def confirm_airdrop(_results):
        recipient = testerchain.client.accounts[-1]
        staker = Staker(checksum_address=recipient,
                        registry=test_registry,
                        is_me=True)

        assert staker.token_balance == NU(45000, 'NU')

        # TODO: Airdrop Testnet Ethers?
        new_eth_balance = original_eth_balance + testerchain.w3.fromWei(
            Felix.ETHER_AIRDROP_AMOUNT, 'ether')
        assert staker.eth_balance == new_eth_balance

    staged_airdrops = Felix._AIRDROP_QUEUE
    next_airdrop = staged_airdrops[0]
    next_airdrop.addCallback(confirm_airdrop)
    yield next_airdrop

    # Felix view
    view_args = ('felix', 'view', '--config-file', configuration_file_location,
                 '--provider', TEST_PROVIDER_URI)
    result = click_runner.invoke(nucypher_cli,
                                 view_args,
                                 catch_exceptions=False,
                                 env=envvars)
    assert result.exit_code == 0
    assert "Address" in result.output
    assert "NU" in result.output
    assert "ETH" in result.output

    # Felix accounts
    accounts_args = ('felix', 'accounts', '--config-file',
                     configuration_file_location, '--provider',
                     TEST_PROVIDER_URI)
    result = click_runner.invoke(nucypher_cli,
                                 accounts_args,
                                 catch_exceptions=False,
                                 env=envvars)
    assert result.exit_code == 0
    assert testerchain.client.accounts[-1] in result.output

    # Felix destroy
    destroy_args = ('felix', 'destroy', '--config-file',
                    configuration_file_location, '--provider',
                    TEST_PROVIDER_URI, '--force')
    result = click_runner.invoke(nucypher_cli,
                                 destroy_args,
                                 catch_exceptions=False,
                                 env=envvars)
    assert result.exit_code == 0
    assert SUCCESSFUL_DESTRUCTION in result.output
    assert not os.path.exists(
        configuration_file_location), "Felix configuration file was deleted"
def test_coexisting_configurations(click_runner,
                                   custom_filepath,
                                   testerchain,
                                   agency_local_registry):
    #
    # Setup
    #

    if custom_filepath.exists():
        shutil.rmtree(str(custom_filepath), ignore_errors=True)
    assert not custom_filepath.exists()

    # Parse node addresses
    # TODO: Is testerchain & Full contract deployment needed here (causes massive slowdown)?
    alice, ursula, another_ursula, felix, staker, *all_yall = testerchain.unassigned_accounts

    envvars = {NUCYPHER_ENVVAR_KEYRING_PASSWORD: INSECURE_DEVELOPMENT_PASSWORD,
               'NUCYPHER_FELIX_DB_SECRET': INSECURE_DEVELOPMENT_PASSWORD}

    # Future configuration filepaths for assertions...
    public_keys_dir = custom_filepath / 'keyring' / 'public'
    known_nodes_dir = custom_filepath / 'known_nodes'

    # ... Ensure they do not exist to begin with.

    # No keys have been generated...
    assert not public_keys_dir.exists()

    # No known nodes exist...
    assert not known_nodes_dir.exists()

    # Not the configuration root...
    assert not os.path.isdir(custom_filepath)

    # ... nothing
    None

    #
    # Create
    #

    # Expected config files
    felix_file_location = custom_filepath / FelixConfiguration.generate_filename()
    alice_file_location = custom_filepath / AliceConfiguration.generate_filename()
    ursula_file_location = custom_filepath / UrsulaConfiguration.generate_filename()
    another_ursula_configuration_file_location = custom_filepath / UrsulaConfiguration.generate_filename(modifier=another_ursula)

    # Felix creates a system configuration
    felix_init_args = ('felix', 'init',
                       '--config-root', custom_filepath,
                       '--network', TEMPORARY_DOMAIN,
                       '--provider', TEST_PROVIDER_URI,
                       '--checksum-address', felix,
                       '--registry-filepath', agency_local_registry.filepath,
                       '--debug')

    result = click_runner.invoke(nucypher_cli, felix_init_args, catch_exceptions=False, env=envvars)
    assert result.exit_code == 0

    # All configuration files still exist.
    assert os.path.isdir(custom_filepath)
    assert os.path.isfile(felix_file_location)
    assert os.path.isdir(public_keys_dir)
    assert len(os.listdir(public_keys_dir)) == 3

    # Use a custom local filepath to init a persistent Alice
    alice_init_args = ('alice', 'init',
                       '--network', TEMPORARY_DOMAIN,
                       '--provider', TEST_PROVIDER_URI,
                       '--pay-with', alice,
                       '--registry-filepath', agency_local_registry.filepath,
                       '--config-root', custom_filepath)

    result = click_runner.invoke(nucypher_cli, alice_init_args, catch_exceptions=False, env=envvars)
    assert result.exit_code == 0

    # All configuration files still exist.
    assert os.path.isfile(felix_file_location)
    assert os.path.isfile(alice_file_location)
    assert len(os.listdir(public_keys_dir)) == 5

    # Use the same local filepath to init a persistent Ursula
    init_args = ('ursula', 'init',
                 '--network', TEMPORARY_DOMAIN,
                 '--provider', TEST_PROVIDER_URI,
                 '--worker-address', ursula,
                 '--rest-host', MOCK_IP_ADDRESS,
                 '--registry-filepath', agency_local_registry.filepath,
                 '--config-root', custom_filepath)

    result = click_runner.invoke(nucypher_cli, init_args, catch_exceptions=False, env=envvars)
    assert result.exit_code == 0

    # All configuration files still exist.
    assert len(os.listdir(public_keys_dir)) == 8
    assert os.path.isfile(felix_file_location)
    assert os.path.isfile(alice_file_location)
    assert os.path.isfile(ursula_file_location)

    # Use the same local filepath to init another persistent Ursula
    init_args = ('ursula', 'init',
                 '--network', TEMPORARY_DOMAIN,
                 '--worker-address', another_ursula,
                 '--rest-host', MOCK_IP_ADDRESS_2,
                 '--registry-filepath', agency_local_registry.filepath,
                 '--provider', TEST_PROVIDER_URI,
                 '--config-root', custom_filepath)

    result = click_runner.invoke(nucypher_cli, init_args, catch_exceptions=False, env=envvars)
    assert result.exit_code == 0

    # All configuration files still exist.
    assert os.path.isfile(felix_file_location)
    assert os.path.isfile(alice_file_location)
    assert os.path.isfile(another_ursula_configuration_file_location)
    assert os.path.isfile(ursula_file_location)
    assert len(os.listdir(public_keys_dir)) == 11

    #
    # Run
    #

    # Run an Ursula amidst the other configuration files
    run_args = ('ursula', 'run',
                '--dry-run',
                '--config-file', another_ursula_configuration_file_location)

    user_input = f'{INSECURE_DEVELOPMENT_PASSWORD}\n' * 2

    Worker.BONDING_POLL_RATE = 1
    Worker.BONDING_TIMEOUT = 1
    with pytest.raises(Teacher.UnbondedWorker):  # TODO: Why is this being checked here?
        # Worker init success, but not bonded.
        result = click_runner.invoke(nucypher_cli, run_args, input=user_input, catch_exceptions=False)
    assert result.exit_code == 0
    Worker.BONDING_TIMEOUT = None

    # All configuration files still exist.
    assert os.path.isfile(felix_file_location)
    assert os.path.isfile(alice_file_location)
    assert os.path.isfile(another_ursula_configuration_file_location)
    assert os.path.isfile(ursula_file_location)
    assert len(os.listdir(public_keys_dir)) == 11

    # Check that the proper Ursula console is attached
    assert another_ursula in result.output

    #
    # Destroy
    #

    another_ursula_destruction_args = ('ursula', 'destroy',
                                       '--force',
                                       '--config-file', another_ursula_configuration_file_location)
    result = click_runner.invoke(nucypher_cli, another_ursula_destruction_args, catch_exceptions=False, env=envvars)
    assert result.exit_code == 0
    assert len(os.listdir(public_keys_dir)) == 8
    assert not os.path.isfile(another_ursula_configuration_file_location)

    ursula_destruction_args = ('ursula', 'destroy', '--config-file', ursula_file_location)
    result = click_runner.invoke(nucypher_cli, ursula_destruction_args, input='Y', catch_exceptions=False, env=envvars)
    assert result.exit_code == 0
    assert 'y/N' in result.output
    assert len(os.listdir(public_keys_dir)) == 5
    assert not os.path.isfile(ursula_file_location)

    alice_destruction_args = ('alice', 'destroy', '--force', '--config-file', alice_file_location)
    result = click_runner.invoke(nucypher_cli, alice_destruction_args, catch_exceptions=False, env=envvars)
    assert result.exit_code == 0
    assert len(os.listdir(public_keys_dir)) == 3
    assert not os.path.isfile(alice_file_location)

    felix_destruction_args = ('felix', 'destroy', '--force', '--config-file', felix_file_location)
    result = click_runner.invoke(nucypher_cli, felix_destruction_args, catch_exceptions=False, env=envvars)
    assert result.exit_code == 0
    assert len(os.listdir(public_keys_dir)) == 0
    assert not os.path.isfile(felix_file_location)
Exemplo n.º 10
0
def felix(click_config, action, teacher_uri, enode, min_stake, network, host,
          dry_run, port, discovery_port, provider_uri, geth, config_root,
          checksum_address, poa, config_file, db_filepath, no_registry,
          registry_filepath, dev, force):

    # Intro
    click.clear()
    if not click_config.quiet:
        click.secho(FELIX_BANNER.format(checksum_address or ''))

    ETH_NODE = NO_BLOCKCHAIN_CONNECTION
    if geth:
        ETH_NODE = actions.get_provider_process(dev=dev)
        provider_uri = ETH_NODE.provider_uri

    if action == "init":
        """Create a brand-new Felix"""

        if not config_root:  # Flag
            config_root = DEFAULT_CONFIG_ROOT  # Envvar or init-only default

        # Acquire Keyring Password
        new_password = click_config.get_password(confirm=True)

        try:
            new_felix_config = FelixConfiguration.generate(
                password=new_password,
                config_root=config_root,
                rest_host=host,
                rest_port=discovery_port,
                db_filepath=db_filepath,
                domains={network} if network else None,
                checksum_address=checksum_address,
                download_registry=not no_registry,
                registry_filepath=registry_filepath,
                provider_uri=provider_uri,
                provider_process=ETH_NODE,
                poa=poa)
        except Exception as e:
            if click_config.debug:
                raise
            else:
                click.secho(str(e), fg='red', bold=True)
                raise click.Abort

        # Paint Help
        painting.paint_new_installation_help(
            new_configuration=new_felix_config,
            config_root=config_root,
            config_file=config_file)

        return  # <-- do not remove (conditional flow control)

    # Domains -> bytes | or default
    domains = [network] if network else None

    # Load Felix from Configuration File with overrides
    try:
        felix_config = FelixConfiguration.from_configuration_file(
            filepath=config_file,
            domains=domains,
            registry_filepath=registry_filepath,
            provider_process=ETH_NODE,
            provider_uri=provider_uri,
            rest_host=host,
            rest_port=port,
            db_filepath=db_filepath,
            poa=poa)

    except FileNotFoundError:
        click.secho(
            f"No Felix configuration file found at {config_file}. "
            f"Check the filepath or run 'nucypher felix init' to create a new system configuration."
        )
        raise click.Abort

    try:

        # Connect to Blockchain
        felix_config.connect_to_blockchain()

        # Authenticate
        password = click_config.get_password(confirm=False)
        click_config.unlock_keyring(character_configuration=felix_config,
                                    password=password)

        # Produce Teacher Ursulas
        teacher_nodes = actions.load_seednodes(
            teacher_uris=[teacher_uri] if teacher_uri else None,
            min_stake=min_stake,
            federated_only=felix_config.federated_only,
            network_domains=felix_config.domains,
            network_middleware=click_config.middleware)

        # Add ETH Bootnode or Peer
        if enode:
            if geth:
                felix_config.blockchain.interface.w3.geth.admin.addPeer(enode)
                click.secho(f"Added ethereum peer {enode}")
            else:
                raise NotImplemented  # TODO: other backends

        # Produce Felix
        FELIX = felix_config.produce(domains=network,
                                     known_nodes=teacher_nodes)
        FELIX.make_web_app()  # attach web application, but dont start service

    except Exception as e:
        if click_config.debug:
            raise
        else:
            click.secho(str(e), fg='red', bold=True)
            raise click.Abort

    if action == "createdb":  # Initialize Database
        if os.path.isfile(FELIX.db_filepath):
            if not force:
                click.confirm("Overwrite existing database?", abort=True)
            os.remove(FELIX.db_filepath)
            click.secho(f"Destroyed existing database {FELIX.db_filepath}")

        FELIX.create_tables()
        click.secho(f"\nCreated new database at {FELIX.db_filepath}",
                    fg='green')

    elif action == 'view':
        token_balance = FELIX.token_balance
        eth_balance = FELIX.eth_balance
        click.secho(f"""
Address .... {FELIX.checksum_address}
NU ......... {str(token_balance)}
ETH ........ {str(eth_balance)}
        """)

    elif action == "accounts":
        accounts = FELIX.blockchain.interface.w3.eth.accounts
        for account in accounts:
            click.secho(account)

    elif action == "destroy":
        """Delete all configuration files from the disk"""
        actions.destroy_configuration(character_config=felix_config,
                                      force=force)

    elif action == 'run':  # Start web services

        try:
            click.secho("Waiting for blockchain sync...", fg='yellow')
            FELIX.blockchain.sync()
            FELIX.start(host=host,
                        port=port,
                        web_services=not dry_run,
                        distribution=True,
                        crash_on_error=click_config.debug)
        finally:
            FELIX.blockchain.disconnect()

    else:
        raise click.BadArgumentUsage("No such argument {}".format(action))
Exemplo n.º 11
0
def felix(click_config, action, teacher_uri, enode, min_stake, network, host,
          dry_run, port, discovery_port, provider_uri, geth, config_root,
          checksum_address, poa, config_file, db_filepath, registry_filepath,
          dev, force):
    """
    "Felix the Faucet" management commands.
    """

    emitter = click_config.emitter

    # Intro
    emitter.clear()
    emitter.banner(FELIX_BANNER.format(checksum_address or ''))

    ETH_NODE = NO_BLOCKCHAIN_CONNECTION
    if geth:
        ETH_NODE = actions.get_provider_process(dev=dev)
        provider_uri = ETH_NODE.provider_uri

    if action == "init":
        """Create a brand-new Felix"""

        if not config_root:  # Flag
            config_root = DEFAULT_CONFIG_ROOT  # Envvar or init-only default

        try:
            new_felix_config = FelixConfiguration.generate(
                password=get_nucypher_password(confirm=True),
                config_root=config_root,
                rest_host=host,
                rest_port=discovery_port,
                db_filepath=db_filepath,
                domains={network} if network else None,
                checksum_address=checksum_address,
                registry_filepath=registry_filepath,
                provider_uri=provider_uri,
                provider_process=ETH_NODE,
                poa=poa)
        except Exception as e:
            if click_config.debug:
                raise
            else:
                emitter.echo(str(e), color='red', bold=True)
                raise click.Abort

        # Paint Help
        painting.paint_new_installation_help(
            emitter, new_configuration=new_felix_config)

        return  # <-- do not remove (conditional flow control)

    # Domains -> bytes | or default
    domains = [network] if network else None

    # Load Felix from Configuration File with overrides
    try:
        felix_config = FelixConfiguration.from_configuration_file(
            filepath=config_file,
            domains=domains,
            registry_filepath=registry_filepath,
            provider_process=ETH_NODE,
            provider_uri=provider_uri,
            rest_host=host,
            rest_port=port,
            db_filepath=db_filepath,
            poa=poa)

    except FileNotFoundError:
        emitter.echo(
            f"No Felix configuration file found at {config_file}. "
            f"Check the filepath or run 'nucypher felix init' to create a new system configuration."
        )
        raise click.Abort

    if action == "destroy":
        """Delete all configuration files from the disk"""
        if dev:
            message = "'nucypher felix destroy' cannot be used in --dev mode"
            raise click.BadOptionUsage(option_name='--dev', message=message)
        actions.destroy_configuration(emitter,
                                      character_config=felix_config,
                                      force=force)
        return

    try:

        # Authenticate
        unlock_nucypher_keyring(emitter,
                                character_configuration=felix_config,
                                password=get_nucypher_password(confirm=False))

        # Produce Teacher Ursulas
        teacher_nodes = actions.load_seednodes(
            emitter,
            teacher_uris=[teacher_uri] if teacher_uri else None,
            min_stake=min_stake,
            federated_only=felix_config.federated_only,
            network_domains=felix_config.domains,
            network_middleware=click_config.middleware)

        # Produce Felix
        FELIX = felix_config.produce(domains=network,
                                     known_nodes=teacher_nodes)
        FELIX.make_web_app()  # attach web application, but dont start service

    except Exception as e:
        if click_config.debug:
            raise
        else:
            emitter.echo(str(e), color='red', bold=True)
            raise click.Abort

    if action == "createdb":  # Initialize Database
        if os.path.isfile(FELIX.db_filepath):
            if not force:
                click.confirm("Overwrite existing database?", abort=True)
            os.remove(FELIX.db_filepath)
            emitter.echo(f"Destroyed existing database {FELIX.db_filepath}")

        FELIX.create_tables()
        emitter.echo(f"\nCreated new database at {FELIX.db_filepath}",
                     color='green')

    elif action == 'view':
        token_balance = FELIX.token_balance
        eth_balance = FELIX.eth_balance
        emitter.echo(f"""
Address .... {FELIX.checksum_address}
NU ......... {str(token_balance)}
ETH ........ {str(eth_balance)}
        """)

    elif action == "accounts":
        accounts = FELIX.blockchain.client.accounts
        for account in accounts:
            emitter.echo(account)

    elif action == 'run':  # Start web services

        emitter.echo("Waiting for blockchain sync...", color='yellow')
        emitter.message(f"Running Felix on {host}:{port}")
        FELIX.start(host=host,
                    port=port,
                    web_services=not dry_run,
                    distribution=True,
                    crash_on_error=click_config.debug)

    else:
        raise click.BadArgumentUsage("No such argument {}".format(action))
Exemplo n.º 12
0
def test_run_felix(click_runner, testerchain, agency, deploy_user_input,
                   mock_primary_registry_filepath):

    clock = Clock()
    Felix._CLOCK = clock
    Felix.DISTRIBUTION_INTERVAL = 5  # seconds
    Felix.DISBURSEMENT_INTERVAL = 0.01  # hours
    Felix.STAGING_DELAY = 2  # seconds

    # Main thread (Flask)
    os.environ['NUCYPHER_FELIX_DB_SECRET'] = INSECURE_DEVELOPMENT_PASSWORD

    # Simulate "Reconnection"
    real_attach_provider = BlockchainDeployerInterface._attach_provider
    cached_blockchain = BlockchainDeployerInterface.reconnect()
    cached_blockchain.registry.commit(filepath=mock_primary_registry_filepath)

    def attach_cached_provider(interface, *args, **kwargs):
        cached_provider = cached_blockchain.provider
        real_attach_provider(interface, provider=cached_provider)

    BlockchainDeployerInterface._attach_provider = attach_cached_provider

    # Mock live contract registry reads
    EthereumContractRegistry.read = lambda *a, **kw: cached_blockchain.registry.read(
    )

    # Test subproc (Click)
    envvars = {
        'NUCYPHER_KEYRING_PASSWORD': INSECURE_DEVELOPMENT_PASSWORD,
        'NUCYPHER_FELIX_DB_SECRET': INSECURE_DEVELOPMENT_PASSWORD,
        'FLASK_DEBUG': '1'
    }

    # Felix creates a system configuration
    init_args = ('--debug', 'felix', 'init', '--registry-filepath',
                 mock_primary_registry_filepath, '--checksum-address',
                 testerchain.client.accounts[0], '--config-root',
                 MOCK_CUSTOM_INSTALLATION_PATH_2, '--network',
                 TEMPORARY_DOMAIN, '--no-registry', '--provider-uri',
                 TEST_PROVIDER_URI)

    result = click_runner.invoke(nucypher_cli,
                                 init_args,
                                 catch_exceptions=False,
                                 env=envvars)
    assert result.exit_code == 0

    configuration_file_location = os.path.join(
        MOCK_CUSTOM_INSTALLATION_PATH_2,
        FelixConfiguration.generate_filename())

    # Felix Creates a Database
    db_args = ('--debug', 'felix', 'createdb', '--registry-filepath',
               mock_primary_registry_filepath, '--config-file',
               configuration_file_location, '--provider-uri',
               TEST_PROVIDER_URI)

    result = click_runner.invoke(nucypher_cli,
                                 db_args,
                                 catch_exceptions=False,
                                 env=envvars)
    assert result.exit_code == 0

    # Felix Runs Web Services
    def run_felix():
        args = ('--debug', 'felix', 'run', '--registry-filepath',
                mock_primary_registry_filepath, '--config-file',
                configuration_file_location, '--provider-uri',
                TEST_PROVIDER_URI, '--dry-run', '--no-registry')

        run_result = click_runner.invoke(nucypher_cli,
                                         args,
                                         catch_exceptions=False,
                                         env=envvars)
        assert run_result.exit_code == 0
        return run_result

    # A (mocked) client requests Felix's services
    def request_felix_landing_page(_result):

        # Init an equal Felix to the already running one.
        felix_config = FelixConfiguration.from_configuration_file(
            filepath=configuration_file_location)

        felix_config.attach_keyring()
        felix_config.keyring.unlock(password=INSECURE_DEVELOPMENT_PASSWORD)
        felix = felix_config.produce()

        # Make a flask app
        web_app = felix.make_web_app()
        test_client = web_app.test_client()

        # Load the landing page
        response = test_client.get('/')
        assert response.status_code == 200

        # Register a new recipient
        response = test_client.post(
            '/register',
            data={'address': felix.blockchain.client.accounts[-1]})
        assert response.status_code == 200

        return

    def time_travel(_result):
        clock.advance(amount=60)

    # Record starting ether balance
    recipient = testerchain.client.accounts[-1]
    staker = Staker(checksum_address=recipient,
                    blockchain=testerchain,
                    is_me=True)
    original_eth_balance = staker.eth_balance

    # Run the callbacks
    d = threads.deferToThread(run_felix)
    d.addCallback(request_felix_landing_page)
    d.addCallback(time_travel)

    yield d

    def confirm_airdrop(_results):
        recipient = testerchain.client.accounts[-1]
        staker = Staker(checksum_address=recipient,
                        blockchain=testerchain,
                        is_me=True)

        assert staker.token_balance == NU(15000, 'NU')

        # TODO: Airdrop Testnet Ethers?
        # new_eth_balance = original_eth_balance + testerchain.w3.fromWei(Felix.ETHER_AIRDROP_AMOUNT, 'ether')
        assert staker.eth_balance == original_eth_balance

    staged_airdrops = Felix._AIRDROP_QUEUE
    next_airdrop = staged_airdrops[0]
    next_airdrop.addCallback(confirm_airdrop)
    yield next_airdrop
Exemplo n.º 13
0
def felix(click_config, action, teacher_uri, min_stake, network, host, dry_run,
          port, discovery_port, provider_uri, config_root, checksum_address,
          poa, config_file, db_filepath, no_registry, registry_filepath,
          force):

    if not click_config.quiet:
        click.secho(FELIX_BANNER.format(checksum_address or ''))

    if action == "init":
        """Create a brand-new Felix"""

        # Validate "Init" Input
        if not network:
            raise click.BadArgumentUsage(
                '--network is required to initialize a new configuration.')

        # Validate "Init" Input
        if not checksum_address:
            raise click.BadArgumentUsage(
                '--checksum-address is required to initialize a new Felix configuration.'
            )

        # Acquire Keyring Password
        if not config_root:  # Flag
            config_root = click_config.config_file  # Envvar
        new_password = click_config.get_password(confirm=True)

        new_felix_config = FelixConfiguration.generate(
            password=new_password,
            config_root=config_root,
            rest_host=host,
            rest_port=discovery_port,
            db_filepath=db_filepath,
            domains={network} if network else None,
            checksum_public_address=checksum_address,
            no_registry=no_registry,
            registry_filepath=registry_filepath,
            provider_uri=provider_uri,
            poa=poa)

        # Paint Help
        painting.paint_new_installation_help(
            new_configuration=new_felix_config,
            config_root=config_root,
            config_file=config_file)

        return  # <-- do not remove (conditional flow control)

    #
    # Authenticated Configurations
    #

    # Domains -> bytes | or default
    domains = [bytes(network, encoding='utf-8')] if network else None

    # Load Felix from Configuration File with overrides
    try:
        felix_config = FelixConfiguration.from_configuration_file(
            filepath=config_file,
            domains=domains,
            registry_filepath=registry_filepath,
            provider_uri=provider_uri,
            rest_host=host,
            rest_port=port,
            db_filepath=db_filepath,
            poa=poa)
    except FileNotFoundError:
        click.secho(
            f"No Felix configuration file found at {config_file}. "
            f"Check the filepath or run 'nucypher felix init' to create a new system configuration."
        )
        raise click.Abort

    else:

        # Produce Teacher Ursulas
        teacher_uris = [teacher_uri] if teacher_uri else list()
        teacher_nodes = actions.load_seednodes(
            teacher_uris=teacher_uris,
            min_stake=min_stake,
            federated_only=False,
            network_middleware=click_config.middleware)

        # Produce Felix
        click_config.unlock_keyring(character_configuration=felix_config)
        FELIX = felix_config.produce(domains=network,
                                     known_nodes=teacher_nodes)
        FELIX.make_web_app()  # attach web application, but dont start service

    if action == "createdb":  # Initialize Database
        if os.path.isfile(FELIX.db_filepath):
            if not force:
                click.confirm("Overwrite existing database?", abort=True)
            os.remove(FELIX.db_filepath)
            click.secho(f"Destroyed existing database {FELIX.db_filepath}")

        FELIX.create_tables()
        click.secho(f"Created new database at {FELIX.db_filepath}")

    elif action == 'view':
        token_balance = FELIX.token_balance
        eth_balance = FELIX.eth_balance
        click.secho(f"""
Address .... {FELIX.checksum_public_address}
NU ......... {str(token_balance)}
ETH ........ {str(eth_balance)}
        """)
        return

    elif action == "accounts":
        accounts = FELIX.blockchain.interface.w3.eth.accounts
        for account in accounts:
            click.secho(account)
        return

    elif action == 'run':  # Start web services
        FELIX.start(host=host,
                    port=port,
                    web_services=not dry_run,
                    distribution=True,
                    crash_on_error=click_config.debug)

    else:  # Error
        raise click.BadArgumentUsage("No such argument {}".format(action))
Exemplo n.º 14
0
def test_coexisting_configurations(click_runner, custom_filepath,
                                   mock_primary_registry_filepath, testerchain,
                                   deploy_user_input):

    # Parse node addresses
    deployer, alice, ursula, another_ursula, *all_yall = testerchain.client.accounts

    envvars = {
        'NUCYPHER_KEYRING_PASSWORD': INSECURE_DEVELOPMENT_PASSWORD,

        # Upgradeable Contracts
        'NUCYPHER_STAKING_ESCROW_SECRET': INSECURE_DEVELOPMENT_PASSWORD,
        'NUCYPHER_POLICY_MANAGER_SECRET': INSECURE_DEVELOPMENT_PASSWORD,
        'NUCYPHER_ADJUDICATOR_SECRET': INSECURE_DEVELOPMENT_PASSWORD,
        'NUCYPHER_USER_ESCROW_PROXY_SECRET': INSECURE_DEVELOPMENT_PASSWORD,

        # Auxiliary
        'NUCYPHER_FELIX_DB_SECRET': INSECURE_DEVELOPMENT_PASSWORD
    }

    # Future configuration filepaths for assertions...
    public_keys_dir = os.path.join(custom_filepath, 'keyring', 'public')
    known_nodes_dir = os.path.join(custom_filepath, 'known_nodes')

    # ... Ensure they do not exist to begin with.
    assert not os.path.isdir(public_keys_dir)
    assert not os.path.isfile(known_nodes_dir)

    # Deploy contracts
    deploy_args = ('contracts', '--registry-outfile',
                   mock_primary_registry_filepath, '--provider-uri',
                   TEST_PROVIDER_URI, '--config-root', custom_filepath,
                   '--poa')

    result = click_runner.invoke(deploy.deploy,
                                 deploy_args,
                                 input=f'0\nY\nDEPLOY',
                                 catch_exceptions=False,
                                 env=envvars)
    assert result.exit_code == 0

    # No keys have been generated...
    with pytest.raises(FileNotFoundError):
        assert len(os.listdir(public_keys_dir)) == 0

    # No known nodes exist...
    with pytest.raises(FileNotFoundError):
        assert len(os.listdir(known_nodes_dir)) == 0

    # Just the configuration root...
    assert os.path.isdir(custom_filepath)

    # and the fresh registry.
    assert os.path.isfile(mock_primary_registry_filepath)

    #
    # Create
    #

    # Expected config files
    felix_file_location = os.path.join(custom_filepath,
                                       FelixConfiguration.generate_filename())
    alice_file_location = os.path.join(custom_filepath,
                                       AliceConfiguration.generate_filename())
    ursula_file_location = os.path.join(
        custom_filepath, UrsulaConfiguration.generate_filename())
    another_ursula_configuration_file_location = os.path.join(
        custom_filepath,
        UrsulaConfiguration.generate_filename(modifier=another_ursula))

    # Felix creates a system configuration
    felix_init_args = ('felix', 'init', '--config-root', custom_filepath,
                       '--network', TEMPORARY_DOMAIN, '--provider-uri',
                       TEST_PROVIDER_URI, '--checksum-address', deployer,
                       '--registry-filepath', mock_primary_registry_filepath)

    result = click_runner.invoke(nucypher_cli,
                                 felix_init_args,
                                 catch_exceptions=False,
                                 env=envvars)
    assert result.exit_code == 0
    assert os.path.isfile(felix_file_location)
    assert len(os.listdir(public_keys_dir)) == 3

    # Use a custom local filepath to init a persistent Alice
    alice_init_args = ('alice', 'init', '--network', TEMPORARY_DOMAIN,
                       '--provider-uri', TEST_PROVIDER_URI, '--pay-with',
                       alice, '--registry-filepath',
                       mock_primary_registry_filepath, '--config-root',
                       custom_filepath)

    result = click_runner.invoke(nucypher_cli,
                                 alice_init_args,
                                 catch_exceptions=False,
                                 env=envvars)
    assert result.exit_code == 0
    assert os.path.isfile(alice_file_location)
    assert len(os.listdir(public_keys_dir)) == 5

    # Use the same local filepath to init a persistent Ursula
    init_args = ('ursula', 'init', '--network', TEMPORARY_DOMAIN,
                 '--provider-uri', TEST_PROVIDER_URI, '--checksum-address',
                 ursula, '--rest-host', MOCK_IP_ADDRESS, '--registry-filepath',
                 mock_primary_registry_filepath, '--config-root',
                 custom_filepath)

    result = click_runner.invoke(nucypher_cli,
                                 init_args,
                                 catch_exceptions=False,
                                 env=envvars)
    assert result.exit_code == 0
    assert len(os.listdir(public_keys_dir)) == 8
    assert os.path.isfile(ursula_file_location)

    # Use the same local filepath to init another persistent Ursula
    init_args = ('ursula', 'init', '--network', TEMPORARY_DOMAIN,
                 '--checksum-address', another_ursula, '--rest-host',
                 MOCK_IP_ADDRESS_2, '--registry-filepath',
                 mock_primary_registry_filepath, '--provider-uri',
                 TEST_PROVIDER_URI, '--config-root', custom_filepath)

    result = click_runner.invoke(nucypher_cli,
                                 init_args,
                                 catch_exceptions=False,
                                 env=envvars)
    assert result.exit_code == 0

    assert os.path.isfile(another_ursula_configuration_file_location)
    assert os.path.isfile(ursula_file_location)

    assert len(os.listdir(public_keys_dir)) == 11

    #
    # Run
    #

    # Run an Ursula amidst the other configuration files
    run_args = ('ursula', 'run', '--dry-run', '--interactive', '--config-file',
                another_ursula_configuration_file_location)

    user_input = f'{INSECURE_DEVELOPMENT_PASSWORD}'
    result = click_runner.invoke(nucypher_cli,
                                 run_args,
                                 input=user_input,
                                 catch_exceptions=False)
    assert result.exit_code == 0

    # Check that the proper Ursula console is attached
    assert another_ursula in result.output

    #
    # Destroy
    #

    another_ursula_destruction_args = (
        'ursula', 'destroy', '--force', '--config-file',
        another_ursula_configuration_file_location)
    result = click_runner.invoke(nucypher_cli,
                                 another_ursula_destruction_args,
                                 catch_exceptions=False,
                                 env=envvars)
    assert result.exit_code == 0
    assert len(os.listdir(public_keys_dir)) == 8
    assert not os.path.isfile(another_ursula_configuration_file_location)

    ursula_destruction_args = ('ursula', 'destroy', '--config-file',
                               ursula_file_location)
    result = click_runner.invoke(nucypher_cli,
                                 ursula_destruction_args,
                                 input='Y',
                                 catch_exceptions=False,
                                 env=envvars)
    assert result.exit_code == 0
    assert 'y/N' in result.output
    assert len(os.listdir(public_keys_dir)) == 5
    assert not os.path.isfile(ursula_file_location)

    felix_destruction_args = ('alice', 'destroy', '--force', '--config-file',
                              alice_file_location)
    result = click_runner.invoke(nucypher_cli,
                                 felix_destruction_args,
                                 catch_exceptions=False,
                                 env=envvars)
    assert result.exit_code == 0
    assert len(os.listdir(public_keys_dir)) == 3
    assert not os.path.isfile(alice_file_location)

    felix_destruction_args = ('felix', 'destroy', '--force', '--config-file',
                              felix_file_location)
    result = click_runner.invoke(nucypher_cli,
                                 felix_destruction_args,
                                 catch_exceptions=False,
                                 env=envvars)
    assert result.exit_code == 0
    assert len(os.listdir(public_keys_dir)) == 0
    assert not os.path.isfile(felix_file_location)
def test_coexisting_configurations(click_runner, custom_filepath,
                                   mock_primary_registry_filepath, testerchain,
                                   test_registry, agency):
    #
    # Setup
    #

    # Parse node addresses
    alice, ursula, another_ursula, felix, staker, *all_yall = testerchain.unassigned_accounts

    envvars = {
        'NUCYPHER_KEYRING_PASSWORD': INSECURE_DEVELOPMENT_PASSWORD,
        'NUCYPHER_FELIX_DB_SECRET': INSECURE_DEVELOPMENT_PASSWORD
    }

    # Future configuration filepaths for assertions...
    public_keys_dir = os.path.join(custom_filepath, 'keyring', 'public')
    known_nodes_dir = os.path.join(custom_filepath, 'known_nodes')

    # ... Ensure they do not exist to begin with.
    assert not os.path.isdir(public_keys_dir)
    assert not os.path.isfile(known_nodes_dir)

    # No keys have been generated...
    with pytest.raises(FileNotFoundError):
        assert len(os.listdir(public_keys_dir)) == 0

    # No known nodes exist...
    with pytest.raises(FileNotFoundError):
        assert len(os.listdir(known_nodes_dir)) == 0

    # Not the configuration root...
    assert not os.path.isdir(custom_filepath)

    #
    # Create
    #

    # Expected config files
    felix_file_location = os.path.join(custom_filepath,
                                       FelixConfiguration.generate_filename())
    alice_file_location = os.path.join(custom_filepath,
                                       AliceConfiguration.generate_filename())
    ursula_file_location = os.path.join(
        custom_filepath, UrsulaConfiguration.generate_filename())
    another_ursula_configuration_file_location = os.path.join(
        custom_filepath,
        UrsulaConfiguration.generate_filename(modifier=another_ursula))

    # Felix creates a system configuration
    felix_init_args = ('felix', 'init', '--config-root', custom_filepath,
                       '--network', TEMPORARY_DOMAIN, '--provider',
                       TEST_PROVIDER_URI, '--checksum-address', felix,
                       '--registry-filepath', mock_primary_registry_filepath,
                       '--debug')

    result = click_runner.invoke(nucypher_cli,
                                 felix_init_args,
                                 catch_exceptions=False,
                                 env=envvars)
    assert result.exit_code == 0

    # All configuration files still exist.
    assert os.path.isdir(custom_filepath)
    assert os.path.isfile(felix_file_location)
    assert os.path.isdir(public_keys_dir)
    assert len(os.listdir(public_keys_dir)) == 3

    # Use a custom local filepath to init a persistent Alice
    alice_init_args = ('alice', 'init', '--network', TEMPORARY_DOMAIN,
                       '--provider', TEST_PROVIDER_URI, '--pay-with', alice,
                       '--registry-filepath', mock_primary_registry_filepath,
                       '--config-root', custom_filepath)

    result = click_runner.invoke(nucypher_cli,
                                 alice_init_args,
                                 catch_exceptions=False,
                                 env=envvars)
    assert result.exit_code == 0

    # All configuration files still exist.
    assert os.path.isfile(felix_file_location)
    assert os.path.isfile(alice_file_location)
    assert len(os.listdir(public_keys_dir)) == 5

    # Use the same local filepath to init a persistent Ursula
    init_args = ('ursula', 'init', '--network', TEMPORARY_DOMAIN, '--provider',
                 TEST_PROVIDER_URI, '--worker-address', ursula,
                 '--staker-address', staker, '--rest-host', MOCK_IP_ADDRESS,
                 '--registry-filepath', mock_primary_registry_filepath,
                 '--config-root', custom_filepath)

    result = click_runner.invoke(nucypher_cli,
                                 init_args,
                                 catch_exceptions=False,
                                 env=envvars)
    assert result.exit_code == 0

    # All configuration files still exist.
    assert len(os.listdir(public_keys_dir)) == 8
    assert os.path.isfile(felix_file_location)
    assert os.path.isfile(alice_file_location)
    assert os.path.isfile(ursula_file_location)

    # Use the same local filepath to init another persistent Ursula
    init_args = ('ursula', 'init', '--network', TEMPORARY_DOMAIN,
                 '--worker-address', another_ursula, '--staker-address',
                 staker, '--rest-host', MOCK_IP_ADDRESS_2,
                 '--registry-filepath', mock_primary_registry_filepath,
                 '--provider', TEST_PROVIDER_URI, '--config-root',
                 custom_filepath)

    result = click_runner.invoke(nucypher_cli,
                                 init_args,
                                 catch_exceptions=False,
                                 env=envvars)
    assert result.exit_code == 0

    # All configuration files still exist.
    assert os.path.isfile(felix_file_location)
    assert os.path.isfile(alice_file_location)
    assert os.path.isfile(another_ursula_configuration_file_location)
    assert os.path.isfile(ursula_file_location)
    assert len(os.listdir(public_keys_dir)) == 11

    #
    # Run
    #

    # Run an Ursula amidst the other configuration files
    run_args = ('ursula', 'run', '--dry-run', '--interactive', '--config-file',
                another_ursula_configuration_file_location)

    user_input = f'{INSECURE_DEVELOPMENT_PASSWORD}\n' * 2
    with pytest.raises(Teacher.DetachedWorker):
        # Worker init success, but unassigned.
        result = click_runner.invoke(nucypher_cli,
                                     run_args,
                                     input=user_input,
                                     catch_exceptions=False)
    assert result.exit_code == 0

    # All configuration files still exist.
    assert os.path.isfile(felix_file_location)
    assert os.path.isfile(alice_file_location)
    assert os.path.isfile(another_ursula_configuration_file_location)
    assert os.path.isfile(ursula_file_location)
    assert len(os.listdir(public_keys_dir)) == 11

    # Check that the proper Ursula console is attached
    assert another_ursula in result.output

    #
    # Destroy
    #

    another_ursula_destruction_args = (
        'ursula', 'destroy', '--force', '--config-file',
        another_ursula_configuration_file_location)
    result = click_runner.invoke(nucypher_cli,
                                 another_ursula_destruction_args,
                                 catch_exceptions=False,
                                 env=envvars)
    assert result.exit_code == 0
    assert len(os.listdir(public_keys_dir)) == 8
    assert not os.path.isfile(another_ursula_configuration_file_location)

    ursula_destruction_args = ('ursula', 'destroy', '--config-file',
                               ursula_file_location)
    result = click_runner.invoke(nucypher_cli,
                                 ursula_destruction_args,
                                 input='Y',
                                 catch_exceptions=False,
                                 env=envvars)
    assert result.exit_code == 0
    assert 'y/N' in result.output
    assert len(os.listdir(public_keys_dir)) == 5
    assert not os.path.isfile(ursula_file_location)

    alice_destruction_args = ('alice', 'destroy', '--force', '--config-file',
                              alice_file_location)
    result = click_runner.invoke(nucypher_cli,
                                 alice_destruction_args,
                                 catch_exceptions=False,
                                 env=envvars)
    assert result.exit_code == 0
    assert len(os.listdir(public_keys_dir)) == 3
    assert not os.path.isfile(alice_file_location)

    felix_destruction_args = ('felix', 'destroy', '--force', '--config-file',
                              felix_file_location)
    result = click_runner.invoke(nucypher_cli,
                                 felix_destruction_args,
                                 catch_exceptions=False,
                                 env=envvars)
    assert result.exit_code == 0
    assert len(os.listdir(public_keys_dir)) == 0
    assert not os.path.isfile(felix_file_location)