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)
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)
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)
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
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
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')
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)
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)
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)
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)
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)
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)
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)
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)
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)
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 )
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)
def stakeholder_configuration(testerchain, agency_local_registry): config = StakeHolderConfiguration( provider_uri=testerchain.provider_uri, registry_filepath=agency_local_registry.filepath) return config
def stakeholder_configuration_file_location(custom_filepath) -> Path: _configuration_file_location = MOCK_CUSTOM_INSTALLATION_PATH / StakeHolderConfiguration.generate_filename( ) return _configuration_file_location
def stakeholder_configuration(testerchain, mock_registry_filepath): config = StakeHolderConfiguration(provider_uri=testerchain.provider_uri, registry_filepath=mock_registry_filepath) return config
def stakeholder_configuration_file_location(custom_filepath): _configuration_file_location = os.path.join( MOCK_CUSTOM_INSTALLATION_PATH, StakeHolderConfiguration.generate_filename()) return _configuration_file_location
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()