Ejemplo n.º 1
0
def test_default_character_configuration_preservation(
        configuration_class, testerchain, test_registry_source_manager,
        tmpdir):

    configuration_class.DEFAULT_CONFIG_ROOT = Path('/tmp')
    fake_address = '0xdeadbeef'
    network = TEMPORARY_DOMAIN

    expected_filename = f'{configuration_class.NAME}.{configuration_class._CONFIG_FILE_EXTENSION}'
    generated_filename = configuration_class.generate_filename()
    assert generated_filename == expected_filename
    expected_filepath = Path('/', 'tmp', generated_filename)

    if expected_filepath.exists():
        expected_filepath.unlink()
    assert not expected_filepath.exists()

    if configuration_class == StakeHolderConfiguration:
        # special case for defaults
        character_config = StakeHolderConfiguration(
            provider_uri=testerchain.provider_uri, domain=network)

    elif configuration_class == UrsulaConfiguration:
        # special case for rest_host & dev mode
        # use keystore
        keystore = Keystore.generate(password=INSECURE_DEVELOPMENT_PASSWORD,
                                     keystore_dir=tmpdir)
        keystore.signing_public_key = SecretKey.random().public_key()
        character_config = configuration_class(checksum_address=fake_address,
                                               domain=network,
                                               rest_host=MOCK_IP_ADDRESS,
                                               keystore=keystore)

    else:
        character_config = configuration_class(checksum_address=fake_address,
                                               domain=network)

    generated_filepath = character_config.generate_filepath()
    assert generated_filepath == expected_filepath

    written_filepath = character_config.to_configuration_file()
    assert written_filepath == expected_filepath
    assert written_filepath.exists()

    try:
        # Read
        with open(character_config.filepath, 'r') as f:
            contents = f.read()

        # Restore from JSON file
        restored_configuration = configuration_class.from_configuration_file()
        assert character_config.serialize(
        ) == restored_configuration.serialize()

        # File still exists after reading
        assert written_filepath.exists()

    finally:
        if expected_filepath.exists():
            expected_filepath.unlink()
def test_default_character_configuration_preservation(
        configuration_class, testerchain, test_registry_source_manager):

    configuration_class.DEFAULT_CONFIG_ROOT = '/tmp'
    fake_address = '0xdeadbeef'
    network = TEMPORARY_DOMAIN

    expected_filename = f'{configuration_class.NAME}.{configuration_class._CONFIG_FILE_EXTENSION}'
    generated_filename = configuration_class.generate_filename()
    assert generated_filename == expected_filename
    expected_filepath = os.path.join('/', 'tmp', generated_filename)

    if os.path.exists(expected_filepath):
        os.remove(expected_filepath)
    assert not os.path.exists(expected_filepath)

    if configuration_class == StakeHolderConfiguration:
        # special case for defaults
        character_config = StakeHolderConfiguration(
            provider_uri=testerchain.provider_uri, domain=network)

    elif configuration_class == UrsulaConfiguration:
        # special case for rest_host & dev mode
        # use keyring
        keyring = Mock(spec=NucypherKeyring)
        keyring.signing_public_key = SecretKey.random().public_key()
        character_config = configuration_class(checksum_address=fake_address,
                                               domain=network,
                                               rest_host=MOCK_IP_ADDRESS,
                                               keyring=keyring)

    else:
        character_config = configuration_class(checksum_address=fake_address,
                                               domain=network)

    generated_filepath = character_config.generate_filepath()
    assert generated_filepath == expected_filepath

    written_filepath = character_config.to_configuration_file()
    assert written_filepath == expected_filepath
    assert os.path.exists(written_filepath)

    try:
        # Read
        with open(character_config.filepath, 'r') as f:
            contents = f.read()

        # Restore from JSON file
        restored_configuration = configuration_class.from_configuration_file()
        assert character_config.serialize(
        ) == restored_configuration.serialize()

        # File still exists after reading
        assert os.path.exists(written_filepath)

    finally:
        if os.path.exists(expected_filepath):
            os.remove(expected_filepath)
Ejemplo n.º 3
0
def deploy(general_config, staker_options, config_file, remote_provider, nucypher_image, seed_network, sentry_dsn, stakes, wipe, prometheus):
    """Deploys NuCypher on existing hardware."""

    emitter = setup_emitter(general_config)

    if not CloudDeployers:
        emitter.echo("Ansible is required to use `nucypher cloudworkers *` commands.  (Please run 'pip install ansible'.)", color="red")
        return
    STAKEHOLDER = staker_options.create_character(emitter, config_file)

    stakers = STAKEHOLDER.get_stakers()
    if not stakers:
        emitter.echo("No staking accounts found.")
        return

    staker_addresses = filter_staker_addresses(stakers, stakes)

    config_file = config_file or StakeHolderConfiguration.default_filepath()

    deployer = CloudDeployers.get_deployer('generic')(emitter, STAKEHOLDER, config_file, remote_provider, nucypher_image, seed_network, sentry_dsn, prometheus=prometheus)

    emitter.echo("found nodes for the following stakers:")
    for staker_address in staker_addresses:
        if deployer.config['instances'].get(staker_address):
            data = deployer.config['instances'].get(staker_address)
            emitter.echo(f'\t{staker_address}: {data["publicaddress"]}', color="yellow")
    deployer.deploy_nucypher_on_existing_nodes(staker_addresses, wipe_nucypher=wipe)
Ejemplo n.º 4
0
def destroy(general_config, staker_options, config_file, cloudprovider,
            stakes):
    """Cleans up all previously created resources for the given netork for the cloud providern"""

    emitter = setup_emitter(general_config)
    if not CloudDeployers:
        emitter.echo(
            "Ansible is required to use `nucypher cloudworkers *` commands.  (Please run 'pip install ansible'.)",
            color="red")
        return
    STAKEHOLDER = staker_options.create_character(emitter, config_file)

    stakers = STAKEHOLDER.get_stakers()
    if not stakers:
        emitter.echo("No staking accounts found.")
        return

    staker_addresses = filter_staker_addresses(stakers, stakes)

    config_file = config_file or StakeHolderConfiguration.default_filepath()

    if not cloudprovider:
        hosts = CloudDeployers.get_deployer('generic')(
            emitter, STAKEHOLDER, config_file).get_all_hosts()
        if len(set(host['provider'] for address, host in hosts)) == 1:
            cloudprovider = hosts[0][1]['provider']
        else:
            emitter.echo(
                "Please specify which provider's hosts you'd like to destroy using --cloudprovider (digitalocean or aws)"
            )
    deployer = CloudDeployers.get_deployer(cloudprovider)(emitter, STAKEHOLDER,
                                                          config_file)
    deployer.destroy_resources(staker_addresses=staker_addresses)
Ejemplo n.º 5
0
def add(general_config, staker_options, config_file, staker_address,
        host_address, login_name, key_path, ssh_port):
    """Creates workers for all stakes owned by the user for the given network."""

    emitter = setup_emitter(general_config)

    STAKEHOLDER = staker_options.create_character(emitter, config_file)

    stakers = STAKEHOLDER.get_stakers()
    if not stakers:
        emitter.echo("No staking accounts found.")
        return

    staker_addresses = filter_staker_addresses(stakers, [staker_address])
    if not staker_addresses:
        emitter.echo(
            f"Could not find staker address: {staker_address} among your stakes. (try `nucypher stake --list`)",
            color="red")
        return

    config_file = config_file or StakeHolderConfiguration.default_filepath()

    deployer = CloudDeployers.get_deployer('generic')(emitter, STAKEHOLDER,
                                                      config_file)
    config = deployer.create_nodes_for_stakers(staker_addresses, host_address,
                                               login_name, key_path, ssh_port)
def test_new_stakeholder(click_runner, custom_filepath, agency_local_registry,
                         testerchain):

    init_args = ('stake', 'init-stakeholder', '--config-root', custom_filepath,
                 '--provider', TEST_PROVIDER_URI, '--network',
                 TEMPORARY_DOMAIN, '--registry-filepath',
                 agency_local_registry.filepath)

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

    # Files and Directories
    assert os.path.isdir(custom_filepath), 'Configuration file does not exist'

    custom_config_filepath = os.path.join(
        custom_filepath, StakeHolderConfiguration.generate_filename())
    assert os.path.isfile(
        custom_config_filepath), 'Configuration file does not exist'

    with open(custom_config_filepath, 'r') as config_file:
        raw_config_data = config_file.read()
        config_data = json.loads(raw_config_data)
        assert config_data['provider_uri'] == TEST_PROVIDER_URI
Ejemplo n.º 7
0
def test_software_stakeholder_configuration(testerchain, test_registry,
                                            stakeholder_configuration,
                                            stakeholder_config_file_location):

    path = stakeholder_config_file_location

    # Save the stakeholder JSON config
    stakeholder_configuration.to_configuration_file(filepath=str(path))
    with open(str(path), 'r') as file:

        # Ensure file contents are serializable
        contents = file.read()
        first_config_contents = json.loads(contents)

    # Destroy this stake holder, leaving only the configuration file behind
    del stakeholder_configuration

    # Restore StakeHolder instance from JSON config
    stakeholder_config = StakeHolderConfiguration.from_configuration_file(
        filepath=path)
    the_same_stakeholder = stakeholder_config.produce()

    # Save the JSON config again
    stakeholder_config.to_configuration_file(filepath=path, override=True)
    with open(stakeholder_config.filepath, 'r') as file:
        contents = file.read()
        second_config_contents = json.loads(contents)

    # Ensure the stakeholder was accurately restored from JSON config
    assert first_config_contents == second_config_contents
Ejemplo n.º 8
0
def init_stakeholder(
        click_config,

        # Other (required)
        provider_uri,

        # Other
        config_root,
        force,

        # Admin Options
        poa,
        light,
        registry_filepath):
    """
    Create a new stakeholder configuration.
    """

    emitter = _setup_emitter(click_config)
    new_stakeholder = StakeHolderConfiguration.generate(
        config_root=config_root,
        provider_uri=provider_uri,
        poa=poa,
        light=light,
        sync=False,
        registry_filepath=registry_filepath)

    filepath = new_stakeholder.to_configuration_file(override=force)
    emitter.echo(f"Wrote new stakeholder configuration to {filepath}",
                 color='green')
Ejemplo n.º 9
0
def add_for_stake(general_config, staker_options, config_file, staker_address,
                  host_address, login_name, key_path, ssh_port, namespace):
    """Sets an existing node as the host for the given staker address."""

    emitter = setup_emitter(general_config)

    STAKEHOLDER = staker_options.create_character(
        emitter, config_file
    )  # FIXME: NameErrors for 'staker options' and 'config_file'

    stakers = STAKEHOLDER.get_stakers()
    if not stakers:
        emitter.echo("No staking accounts found.")
        return

    staker_addresses = filter_staker_addresses(stakers, [staker_address])
    if not staker_addresses:
        emitter.echo(
            f"Could not find staker address: {staker_address} among your stakes. (try `nucypher stake --list`)",
            color="red")
        return

    config_file = config_file or StakeHolderConfiguration.default_filepath()

    deployer = CloudDeployers.get_deployer('generic')(
        emitter,
        STAKEHOLDER,
        config_file,
        namespace=namespace,
        network=STAKEHOLDER.network,
        action='add_for_stake')
    config = deployer.create_nodes(staker_addresses, host_address, login_name,
                                   key_path, ssh_port)
Ejemplo n.º 10
0
def up(general_config, staker_options, config_file, cloudprovider, aws_profile, remote_provider, nucypher_image, seed_network, sentry_dsn, stakes, wipe, prometheus):
    """Creates workers for all stakes owned by the user for the given network."""

    emitter = setup_emitter(general_config)

    if not CloudDeployers:
        emitter.echo("Ansible is required to use this command.  (Please run 'pip install ansible'.)", color="red")
        return
    STAKEHOLDER = staker_options.create_character(emitter, config_file)

    stakers = STAKEHOLDER.get_stakers()
    if not stakers:
        emitter.echo("No staking accounts found.")
        return

    staker_addresses = filter_staker_addresses(stakers, stakes)

    config_file = config_file or StakeHolderConfiguration.default_filepath()

    deployer = CloudDeployers.get_deployer(cloudprovider)(emitter, STAKEHOLDER, config_file, remote_provider, nucypher_image, seed_network, sentry_dsn, aws_profile, prometheus)
    config = deployer.create_nodes_for_stakers(staker_addresses)

    if config.get('instances') and len(config.get('instances')) >= len(staker_addresses):
        emitter.echo('Nodes exist for all requested stakes', color="yellow")
        deployer.deploy_nucypher_on_existing_nodes(staker_addresses, wipe_nucypher=wipe)
Ejemplo n.º 11
0
def test_default_character_configuration_preservation(
        configuration_class, testerchain, test_registry_source_manager):

    configuration_class.DEFAULT_CONFIG_ROOT = '/tmp'
    fake_address = '0xdeadbeef'
    network = TEMPORARY_DOMAIN

    expected_filename = f'{configuration_class.NAME}.{configuration_class._CONFIG_FILE_EXTENSION}'
    generated_filename = configuration_class.generate_filename()
    assert generated_filename == expected_filename
    expected_filepath = os.path.join('/', 'tmp', generated_filename)

    if os.path.exists(expected_filepath):
        os.remove(expected_filepath)
    assert not os.path.exists(expected_filepath)

    if configuration_class == StakeHolderConfiguration:
        # special case for defaults
        character_config = StakeHolderConfiguration(
            provider_uri=testerchain.provider_uri, domains={network})
    else:
        character_config = configuration_class(checksum_address=fake_address,
                                               domains={network})

    generated_filepath = character_config.generate_filepath()
    assert generated_filepath == expected_filepath

    written_filepath = character_config.to_configuration_file()
    assert written_filepath == expected_filepath
    assert os.path.exists(written_filepath)

    try:
        # Read
        with open(character_config.filepath, 'r') as f:
            contents = f.read()

        # Restore from JSON file
        restored_configuration = configuration_class.from_configuration_file()
        assert character_config.serialize(
        ) == restored_configuration.serialize()

        # File still exists after reading
        assert os.path.exists(written_filepath)

    finally:
        if os.path.exists(expected_filepath):
            os.remove(expected_filepath)
Ejemplo n.º 12
0
def config(general_config, config_file, config_options):
    """View and optionally update existing StakeHolder's configuration."""
    emitter = setup_emitter(general_config)
    configuration_file_location = config_file or StakeHolderConfiguration.default_filepath()
    updates = config_options.get_updates()
    get_or_update_configuration(emitter=emitter,
                                config_class=StakeHolderConfiguration,
                                filepath=configuration_file_location,
                                updates=updates)
Ejemplo n.º 13
0
def config(general_config, config_file, config_options):
    """View and optionally update existing StakeHolder's configuration."""
    emitter = _setup_emitter(general_config)
    configuration_file_location = config_file or StakeHolderConfiguration.default_filepath()
    emitter.echo(f"StakeHolder Configuration {configuration_file_location} \n {'='*55}")
    return get_or_update_configuration(emitter=emitter,
                                       config_class=StakeHolderConfiguration,
                                       filepath=configuration_file_location,
                                       config_options=config_options)
Ejemplo n.º 14
0
def _get_stakeholder_config(config_file, provider_uri, poa, light, registry_filepath):
    try:
        stakeholder_config = StakeHolderConfiguration.from_configuration_file(filepath=config_file,
                                                                              provider_uri=provider_uri,
                                                                              poa=poa,
                                                                              light=light,
                                                                              sync=False,
                                                                              registry_filepath=registry_filepath)

        return stakeholder_config
    except FileNotFoundError:
        return actions.handle_missing_configuration_file(character_config_class=StakeHolderConfiguration,
                                                         config_file=config_file)
Ejemplo n.º 15
0
def status(general_config, staker_options, config_file, cloudprovider, stakes):
    """Displays worker status and updates worker data in stakeholder config"""

    emitter = setup_emitter(general_config)
    if not CloudDeployers:
        emitter.echo("Ansible is required to use `nucypher cloudworkers *` commands.  (Please run 'pip install ansible'.)", color="red")
        return
    STAKEHOLDER = staker_options.create_character(emitter, config_file)
    config_file = config_file or StakeHolderConfiguration.default_filepath()
    deployer = CloudDeployers.get_deployer(cloudprovider)(emitter, STAKEHOLDER, config_file)

    stakers = STAKEHOLDER.get_stakers()
    staker_addresses = filter_staker_addresses(stakers, stakes)

    deployer.get_worker_status(staker_addresses)
Ejemplo n.º 16
0
    def create_config(self, emitter, config_file):
        try:
            return StakeHolderConfiguration.from_configuration_file(
                emitter=emitter,
                filepath=config_file,
                provider_uri=self.provider_uri,
                poa=self.poa,
                light=self.light,
                sync=False,
                domains={self.network} if self.network else None,  # TODO: #1580
                registry_filepath=self.registry_filepath)

        except FileNotFoundError:
            return actions.handle_missing_configuration_file(
                character_config_class=StakeHolderConfiguration,
                init_command_hint=f"{stake.name} {init_stakeholder.name}",
                config_file=config_file)
Ejemplo n.º 17
0
def destroy(general_config, staker_options, config_file, cloudprovider, stakes):
    """Cleans up all previously created resources for the given netork for the cloud providern"""

    emitter = setup_emitter(general_config)
    if not CloudDeployers:
        emitter.echo("Ansible is required to use `nucypher cloudworkers *` commands.  (Please run 'pip install ansible'.)", color="red")
        return
    STAKEHOLDER = staker_options.create_character(emitter, config_file)

    stakers = STAKEHOLDER.get_stakers()
    if not stakers:
        emitter.echo("No staking accounts found.")
        return

    staker_addresses = filter_staker_addresses(stakers, stakes)

    config_file = config_file or StakeHolderConfiguration.default_filepath()
    deployer = CloudDeployers.get_deployer(cloudprovider)(emitter, STAKEHOLDER, config_file)
    deployer.destroy_resources(staker_addresses=staker_addresses)
Ejemplo n.º 18
0
    def generate_config(self, config_root):

        if self.provider_uri is None:
            raise click.BadOptionUsage(
                option_name="--provider",
                message="--provider must be specified to create a new stakeholder")

        if self.network is None:
            raise click.BadOptionUsage(
                option_name="--network",
                message="--network must be specified to create a new stakeholder")

        return StakeHolderConfiguration.generate(
            config_root=config_root,
            provider_uri=self.provider_uri,
            poa=self.poa,
            light=self.light,
            sync=False,
            registry_filepath=self.registry_filepath,
            domains={self.network}  # TODO: #1580
        )
Ejemplo n.º 19
0
def stake(click_config,
          action,

          config_root,
          config_file,

          # Mode
          force,
          offline,
          hw_wallet,

          # Blockchain
          poa,
          registry_filepath,
          provider_uri,
          sync,

          # Stake
          staking_address,
          worker_address,
          withdraw_address,
          value,
          lock_periods,
          index,
          policy_reward,
          staking_reward,
          enable,
          lock_until,

          ) -> None:
    """
    Manage stakes and other staker-related operations.

    \b
    Actions
    -------------------------------------------------
    init-stakeholder  Create a new stakeholder configuration
    list              List active stakes for current stakeholder
    accounts          Show ETH and NU balances for stakeholder's accounts
    sync              Synchronize stake data with on-chain information
    set-worker        Bond a worker to a staker
    detach-worker     Detach worker currently bonded to a staker
    init              Create a new stake
    restake           Manage re-staking with --enable or --disable
    divide            Create a new stake from part of an existing one
    collect-reward    Withdraw staking reward

    """

    # Banner
    emitter = click_config.emitter
    emitter.clear()
    emitter.banner(StakeHolder.banner)

    if action == 'init-stakeholder':

        if not provider_uri:
            raise click.BadOptionUsage(option_name='--provider',
                                       message="--provider is required to create a new stakeholder.")

        new_stakeholder = StakeHolderConfiguration.generate(config_root=config_root,
                                                            provider_uri=provider_uri,
                                                            poa=poa,
                                                            sync=False,
                                                            registry_filepath=registry_filepath)

        filepath = new_stakeholder.to_configuration_file(override=force)
        emitter.echo(f"Wrote new stakeholder configuration to {filepath}", color='green')
        return  # Exit

    try:
        stakeholder_config = StakeHolderConfiguration.from_configuration_file(filepath=config_file,
                                                                              provider_uri=provider_uri,
                                                                              poa=poa,
                                                                              sync=False,
                                                                              registry_filepath=registry_filepath)
    except FileNotFoundError:
        return actions.handle_missing_configuration_file(character_config_class=StakeHolderConfiguration,
                                                         config_file=config_file)

    #
    # Make Stakeholder
    #

    STAKEHOLDER = stakeholder_config.produce(initial_address=staking_address)
    blockchain = BlockchainInterfaceFactory.get_interface(provider_uri=provider_uri)  # Eager connection
    economics = STAKEHOLDER.economics

    # Dynamic click types (Economics)
    min_locked = economics.minimum_allowed_locked
    stake_value_range = click.FloatRange(min=NU.from_nunits(min_locked).to_tokens(), clamp=False)
    stake_duration_range = click.IntRange(min=economics.minimum_locked_periods, clamp=False)
    stake_extension_range = click.IntRange(min=1, max=economics.maximum_allowed_locked, clamp=False)

    #
    # Eager Actions
    #

    if action == 'list':
        stakes = STAKEHOLDER.all_stakes
        if not stakes:
            emitter.echo(f"There are no active stakes")
        else:
            painting.paint_stakes(emitter=emitter, stakes=stakes)
        return  # Exit

    elif action == 'accounts':
        for address, balances in STAKEHOLDER.wallet.balances.items():
            emitter.echo(f"{address} | {Web3.fromWei(balances['ETH'], 'ether')} ETH | {NU.from_nunits(balances['NU'])}")
        return  # Exit

    elif action == 'set-worker':

        if not staking_address:
            staking_address = select_stake(stakeholder=STAKEHOLDER, emitter=emitter).staker_address

        if not worker_address:
            worker_address = click.prompt("Enter worker address", type=EIP55_CHECKSUM_ADDRESS)

        # TODO: Check preconditions (e.g., minWorkerPeriods, already in use, etc)

        password = None
        if not hw_wallet and not blockchain.client.is_local:
            password = get_client_password(checksum_address=staking_address)

        STAKEHOLDER.assimilate(checksum_address=staking_address, password=password)
        receipt = STAKEHOLDER.set_worker(worker_address=worker_address)

        # TODO: Double-check dates
        current_period = STAKEHOLDER.staking_agent.get_current_period()
        bonded_date = datetime_at_period(period=current_period, seconds_per_period=economics.seconds_per_period)
        min_worker_periods = STAKEHOLDER.staking_agent.staking_parameters()[7]
        release_period = current_period + min_worker_periods
        release_date = datetime_at_period(period=release_period, seconds_per_period=economics.seconds_per_period)

        emitter.echo(f"\nWorker {worker_address} successfully bonded to staker {staking_address}", color='green')
        paint_receipt_summary(emitter=emitter,
                              receipt=receipt,
                              chain_name=blockchain.client.chain_name,
                              transaction_type='set_worker')
        emitter.echo(f"Bonded at period #{current_period} ({bonded_date})", color='green')
        emitter.echo(f"This worker can be replaced or detached after period "
                     f"#{release_period} ({release_date})", color='green')
        return  # Exit

    elif action == 'detach-worker':

        if not staking_address:
            staking_address = select_stake(stakeholder=STAKEHOLDER, emitter=emitter).staker_address

        if worker_address:
            raise click.BadOptionUsage(message="detach-worker cannot be used together with --worker-address",
                                       option_name='--worker-address')

        # TODO: Check preconditions (e.g., minWorkerPeriods)

        worker_address = STAKEHOLDER.staking_agent.get_worker_from_staker(staking_address)

        password = None
        if not hw_wallet and not blockchain.client.is_local:
            password = get_client_password(checksum_address=staking_address)

        # TODO: Create Stakeholder.detach_worker() and use it here
        STAKEHOLDER.assimilate(checksum_address=staking_address, password=password)
        receipt = STAKEHOLDER.set_worker(worker_address=BlockchainInterface.NULL_ADDRESS)

        # TODO: Double-check dates
        current_period = STAKEHOLDER.staking_agent.get_current_period()
        bonded_date = datetime_at_period(period=current_period, seconds_per_period=economics.seconds_per_period)

        emitter.echo(f"Successfully detached worker {worker_address} from staker {staking_address}", color='green')
        paint_receipt_summary(emitter=emitter,
                              receipt=receipt,
                              chain_name=blockchain.client.chain_name,
                              transaction_type='detach_worker')
        emitter.echo(f"Detached at period #{current_period} ({bonded_date})", color='green')
        return  # Exit

    elif action == 'create':
        """Initialize a new stake"""

        #
        # Get Staking Account
        #

        password = None
        if not staking_address:
            staking_address = select_client_account(prompt="Select staking account",
                                                    emitter=emitter,
                                                    provider_uri=STAKEHOLDER.wallet.blockchain.provider_uri)

        if not hw_wallet and not blockchain.client.is_local:
            password = click.prompt(f"Enter password to unlock {staking_address}",
                                    hide_input=True,
                                    confirmation_prompt=False)
        #
        # Stage Stake
        #

        if not value:
            value = click.prompt(f"Enter stake value in NU",
                                 type=stake_value_range,
                                 default=NU.from_nunits(min_locked).to_tokens())
        value = NU.from_tokens(value)

        if not lock_periods:
            prompt = f"Enter stake duration ({STAKEHOLDER.economics.minimum_locked_periods} periods minimum)"
            lock_periods = click.prompt(prompt, type=stake_duration_range)

        start_period = STAKEHOLDER.staking_agent.get_current_period()
        end_period = start_period + lock_periods

        #
        # Review
        #

        if not force:
            painting.paint_staged_stake(emitter=emitter,
                                        stakeholder=STAKEHOLDER,
                                        staking_address=staking_address,
                                        stake_value=value,
                                        lock_periods=lock_periods,
                                        start_period=start_period,
                                        end_period=end_period)

            confirm_staged_stake(staker_address=staking_address, value=value, lock_periods=lock_periods)

        # Last chance to bail
        click.confirm("Publish staged stake to the blockchain?", abort=True)

        # Execute
        STAKEHOLDER.assimilate(checksum_address=staking_address, password=password)
        new_stake = STAKEHOLDER.initialize_stake(amount=value, lock_periods=lock_periods)

        painting.paint_staking_confirmation(emitter=emitter,
                                            ursula=STAKEHOLDER,
                                            transactions=new_stake.transactions)
        return  # Exit

    elif action == "restake":

        # Authenticate
        if not staking_address:
            staking_address = select_stake(stakeholder=STAKEHOLDER, emitter=emitter).staker_address
        password = None
        if not hw_wallet and not blockchain.client.is_local:
            password = get_client_password(checksum_address=staking_address)
        STAKEHOLDER.assimilate(checksum_address=staking_address, password=password)

        # Inner Exclusive Switch
        if lock_until:
            if not force:
                confirm_enable_restaking_lock(emitter, staking_address=staking_address, release_period=lock_until)
            receipt = STAKEHOLDER.enable_restaking_lock(release_period=lock_until)
            emitter.echo(f'Successfully enabled re-staking lock for {staking_address} until {lock_until}',
                         color='green', verbosity=1)
        elif enable:
            if not force:
                confirm_enable_restaking(emitter, staking_address=staking_address)
            receipt = STAKEHOLDER.enable_restaking()
            emitter.echo(f'Successfully enabled re-staking for {staking_address}', color='green', verbosity=1)
        else:
            if not force:
                click.confirm(f"Confirm disable re-staking for staker {staking_address}?", abort=True)
            receipt = STAKEHOLDER.disable_restaking()
            emitter.echo(f'Successfully disabled re-staking for {staking_address}', color='green', verbosity=1)

        paint_receipt_summary(receipt=receipt, emitter=emitter, chain_name=blockchain.client.chain_name)
        return  # Exit

    elif action == 'divide':
        """Divide an existing stake by specifying the new target value and end period"""

        if staking_address and index is not None:  # 0 is valid.
            current_stake = STAKEHOLDER.stakes[index]
        else:
            current_stake = select_stake(stakeholder=STAKEHOLDER, emitter=emitter)

        #
        # Stage Stake
        #

        # Value
        if not value:
            value = click.prompt(f"Enter target value (must be less than or equal to {str(current_stake.value)})",
                                 type=stake_value_range)
        value = NU(value, 'NU')

        # Duration
        if not lock_periods:
            extension = click.prompt("Enter number of periods to extend", type=stake_extension_range)
        else:
            extension = lock_periods

        if not force:
            painting.paint_staged_stake_division(emitter=emitter,
                                                 stakeholder=STAKEHOLDER,
                                                 original_stake=current_stake,
                                                 target_value=value,
                                                 extension=extension)
            click.confirm("Is this correct?", abort=True)

        # Execute
        password = None
        if not hw_wallet and not blockchain.client.is_local:
            password = get_client_password(checksum_address=current_stake.staker_address)

        STAKEHOLDER.assimilate(checksum_address=current_stake.staker_address, password=password)
        modified_stake, new_stake = STAKEHOLDER.divide_stake(stake_index=current_stake.index,
                                                             target_value=value,
                                                             additional_periods=extension)
        emitter.echo('Successfully divided stake', color='green', verbosity=1)
        paint_receipt_summary(emitter=emitter,
                              receipt=new_stake.receipt,
                              chain_name=blockchain.client.chain_name)

        # Show the resulting stake list
        painting.paint_stakes(emitter=emitter, stakes=STAKEHOLDER.stakes)
        return  # Exit

    elif action == 'collect-reward':
        """Withdraw staking reward to the specified wallet address"""

        password = None
        if not hw_wallet and not blockchain.client.is_local:
            password = get_client_password(checksum_address=staking_address)

        if not staking_reward and not policy_reward:
            raise click.BadArgumentUsage(f"Either --staking-reward or --policy-reward must be True to collect rewards.")

        STAKEHOLDER.assimilate(checksum_address=staking_address, password=password)
        if staking_reward:
            # Note: Sending staking / inflation rewards to another account is not allowed.
            staking_receipt = STAKEHOLDER.collect_staking_reward()
            paint_receipt_summary(receipt=staking_receipt,
                                  chain_name=STAKEHOLDER.wallet.blockchain.client.chain_name,
                                  emitter=emitter)

        if policy_reward:
            policy_receipt = STAKEHOLDER.collect_policy_reward(collector_address=withdraw_address)
            paint_receipt_summary(receipt=policy_receipt,
                                  chain_name=STAKEHOLDER.wallet.blockchain.client.chain_name,
                                  emitter=emitter)

        return  # Exit

    # Catch-All for unknown actions
    else:
        ctx = click.get_current_context()
        raise click.UsageError(message=f"Unknown action '{action}'.", ctx=ctx)
Ejemplo n.º 20
0
def stakeholder_configuration(testerchain, agency_local_registry):
    config = StakeHolderConfiguration(
        provider_uri=testerchain.provider_uri,
        registry_filepath=agency_local_registry.filepath)
    return config
Ejemplo n.º 21
0
def stakeholder_configuration_file_location(custom_filepath) -> Path:
    _configuration_file_location = MOCK_CUSTOM_INSTALLATION_PATH / StakeHolderConfiguration.generate_filename(
    )
    return _configuration_file_location
Ejemplo n.º 22
0
def stakeholder_configuration(testerchain, mock_registry_filepath):
    config = StakeHolderConfiguration(provider_uri=testerchain.provider_uri,
                                      registry_filepath=mock_registry_filepath)
    return config
Ejemplo n.º 23
0
def stakeholder_configuration_file_location(custom_filepath):
    _configuration_file_location = os.path.join(
        MOCK_CUSTOM_INSTALLATION_PATH,
        StakeHolderConfiguration.generate_filename())
    return _configuration_file_location
Ejemplo n.º 24
0
def test_ursula_and_local_keystore_signer_integration(
        click_runner, tmp_path, manual_staker, stake_value, token_economics,
        mocker, mock_funded_account_password_keystore, testerchain):
    config_root_path = tmp_path
    ursula_config_path = config_root_path / UrsulaConfiguration.generate_filename(
    )
    stakeholder_config_path = config_root_path / StakeHolderConfiguration.generate_filename(
    )
    worker_account, password, mock_keystore_path = mock_funded_account_password_keystore

    #
    # Stakeholder Steps
    #

    init_args = ('stake', 'init-stakeholder', '--config-root',
                 config_root_path, '--provider', TEST_PROVIDER_URI,
                 '--network', TEMPORARY_DOMAIN)
    click_runner.invoke(nucypher_cli, init_args, catch_exceptions=False)

    stake_args = ('stake', 'create', '--config-file', stakeholder_config_path,
                  '--staking-address', manual_staker, '--value',
                  stake_value.to_tokens(), '--lock-periods',
                  token_economics.minimum_locked_periods, '--force')
    # TODO: Is This test is writing to the default system directory and ignoring updates to the passed filepath?
    user_input = f'0\n{password}\nY\n'
    click_runner.invoke(nucypher_cli,
                        stake_args,
                        input=user_input,
                        catch_exceptions=False)

    init_args = ('stake', 'bond-worker', '--config-file',
                 stakeholder_config_path, '--staking-address', manual_staker,
                 '--worker-address', worker_account.address, '--force')
    user_input = password
    click_runner.invoke(nucypher_cli,
                        init_args,
                        input=user_input,
                        catch_exceptions=False)

    #
    # Worker Steps
    #

    # Good signer...
    mock_signer_uri = f'keystore:{mock_keystore_path}'
    pre_config_signer = KeystoreSigner.from_signer_uri(uri=mock_signer_uri,
                                                       testnet=True)
    assert worker_account.address in pre_config_signer.accounts

    deploy_port = select_test_port()

    init_args = (
        'ursula',
        'init',
        '--network',
        TEMPORARY_DOMAIN,
        '--worker-address',
        worker_account.address,
        '--config-root',
        config_root_path,
        '--provider',
        TEST_PROVIDER_URI,
        '--rest-host',
        MOCK_IP_ADDRESS,
        '--rest-port',
        deploy_port,

        # The bit we are testing for here
        '--signer',
        mock_signer_uri)

    cli_env = {
        NUCYPHER_ENVVAR_KEYRING_PASSWORD: password,
        NUCYPHER_ENVVAR_WORKER_ETH_PASSWORD: password,
    }
    result = click_runner.invoke(nucypher_cli,
                                 init_args,
                                 catch_exceptions=False,
                                 env=cli_env)
    assert result.exit_code == 0, result.stdout

    # Inspect the configuration file for the signer URI
    with open(ursula_config_path, 'r') as config_file:
        raw_config_data = config_file.read()
        config_data = json.loads(raw_config_data)
        assert config_data['signer_uri'] == mock_signer_uri,\
            "Keystore URI was not correctly included in configuration file"

    # Recreate a configuration with the signer URI preserved
    ursula_config = UrsulaConfiguration.from_configuration_file(
        ursula_config_path)
    assert ursula_config.signer_uri == mock_signer_uri

    # Mock decryption of web3 client keyring
    mocker.patch.object(Account,
                        'decrypt',
                        return_value=worker_account.privateKey)
    ursula_config.attach_keyring(checksum_address=worker_account.address)
    ursula_config.keyring.unlock(password=password)

    # Produce an Ursula with a Keystore signer correctly derived from the signer URI, and don't do anything else!
    mocker.patch.object(StakeList, 'refresh', autospec=True)
    ursula = ursula_config.produce(client_password=password,
                                   commit_now=False,
                                   block_until_ready=False)

    try:
        # Verify the keystore path is still preserved
        assert isinstance(ursula.signer, KeystoreSigner)
        assert isinstance(ursula.signer.path, str), "Use str"
        assert ursula.signer.path == str(mock_keystore_path)

        # Show that we can produce the exact same signer as pre-config...
        assert pre_config_signer.path == ursula.signer.path

        # ...and that transactions are signed by the keystore signer
        txhash = ursula.commit_to_next_period()
        receipt = testerchain.wait_for_receipt(txhash)
        transaction_data = testerchain.client.w3.eth.getTransaction(
            receipt['transactionHash'])
        assert transaction_data['from'] == worker_account.address
    finally:
        ursula.stop()