def run(general_config, character_options, config_file, controller_port, dry_run): """Start Alice's web controller.""" # Setup emitter = setup_emitter(general_config) ALICE = character_options.create_character(emitter, config_file, general_config.json_ipc) try: # RPC if general_config.json_ipc: rpc_controller = ALICE.make_rpc_controller() _transport = rpc_controller.make_control_transport() rpc_controller.start() return # HTTP else: emitter.message(f"Alice Verifying Key {bytes(ALICE.stamp).hex()}", color="green", bold=True) controller = ALICE.make_web_controller( crash_on_error=general_config.debug) ALICE.log.info('Starting HTTP Character Web Controller') emitter.message( f'Running HTTP Alice Controller at http://localhost:{controller_port}' ) return controller.start(http_port=controller_port, dry_run=dry_run) # Handle Crash except Exception as e: ALICE.log.critical(str(e)) emitter.message(f"{e.__class__.__name__} {e}", color='red', bold=True) if general_config.debug: raise # Crash :-(
def retrieve(general_config, character_options, config_file, label, policy_encrypting_key, alice_verifying_key, message_kit, ipfs, alice, force): """Obtain plaintext from encrypted data, if access was granted.""" # Setup emitter = setup_emitter(general_config) BOB = character_options.create_character(emitter, config_file) if not message_kit: if ipfs: prompt = "Enter IPFS CID for encrypted data" else: prompt = "Enter encrypted data (base64)" message_kit = click.prompt(prompt, type=click.STRING) if ipfs: import ipfshttpclient # TODO: #2108 emitter.message(f"Connecting to IPFS Gateway {ipfs}") ipfs_client = ipfshttpclient.connect(ipfs) cid = message_kit # Understand the message kit value as an IPFS hash. raw_message_kit = ipfs_client.cat( cid) # cat the contents at the hash reference emitter.message(f"Downloaded message kit from IPFS (CID {cid})", color='green') message_kit = raw_message_kit.decode() # cast to utf-8 if not alice_verifying_key: if alice: # from storage card = Card.load(identifier=alice) if card.character is not Alice: emitter.error('Grantee card is not an Alice.') raise click.Abort alice_verifying_key = card.verifying_key.hex() emitter.message( f'{card.nickname or ("Alice #"+card.id.hex())}\n' f'Verifying Key | {card.verifying_key.hex()}', color='green') if not force: click.confirm('Is this the correct Granter (Alice)?', abort=True) else: # interactive alice_verifying_key = click.prompt("Enter Alice's verifying key", click.STRING) if not force: if not policy_encrypting_key: policy_encrypting_key = click.prompt("Enter policy public key", type=click.STRING) if not label: label = click.prompt("Enter label to retrieve", type=click.STRING) # Request bob_request_data = { 'label': label, 'policy_encrypting_key': policy_encrypting_key, 'alice_verifying_key': alice_verifying_key, 'message_kit': message_kit, } response = BOB.controller.retrieve(request=bob_request_data) # TODO: Uncomment for Demo. Fix Cleartext Deserialization. # for cleartext in response['cleartexts']: # print(b64decode(cleartext)) return response
def grant(general_config, bob, bob_encrypting_key, bob_verifying_key, label, value, rate, expiration, m, n, character_options, config_file, force): """Create and enact an access policy for some Bob. """ # Setup emitter = setup_emitter(general_config) ALICE = character_options.create_character(emitter, config_file, general_config.json_ipc) # Policy option validation if ALICE.federated_only: if any((value, rate)): message = "Can't use --value or --rate with a federated Alice." raise click.BadOptionUsage(option_name="--value, --rate", message=message) elif bool(value) and bool(rate): raise click.BadOptionUsage(option_name="--rate", message="Can't use --value if using --rate") # Grantee selection if bob and any((bob_encrypting_key, bob_verifying_key)): message = '--bob cannot be used with --bob-encrypting-key or --bob-veryfying key' raise click.BadOptionUsage(option_name='--bob', message=message) if bob: card = Card.load(identifier=bob) if card.character is not Bob: emitter.error('Grantee card is not a Bob.') raise click.Abort paint_single_card(emitter=emitter, card=card) if not force: click.confirm('Is this the correct grantee (Bob)?', abort=True) bob_encrypting_key = card.encrypting_key.hex() bob_verifying_key = card.verifying_key.hex() # Interactive collection follows: # TODO: Extricate to support modules # - Disclaimer # - Label # - Expiration Date & Time # - M of N # - Policy Value (ETH) # Policy Expiration # TODO: Remove this line when the time is right. paint_probationary_period_disclaimer(emitter) # Label if not label: label = click.prompt(f'Enter label to grant Bob {bob_verifying_key[:8]}', type=click.STRING) if not force and not expiration: if ALICE.duration_periods: # TODO: use a default in days or periods? expiration = maya.now() + timedelta(days=ALICE.duration_periods) # default if not click.confirm(f'Use default policy duration (expires {expiration})?'): expiration = click.prompt('Enter policy expiration datetime', type=click.DateTime()) else: # No policy duration default default available; Go interactive expiration = click.prompt('Enter policy expiration datetime', type=click.DateTime()) # TODO: Remove this line when the time is right. enforce_probationary_period(emitter=emitter, expiration=expiration) # Policy Threshold and Shares if not n: n = ALICE.n if not force and not click.confirm(f'Use default value for N ({n})?', default=True): n = click.prompt('Enter total number of shares (N)', type=click.INT) if not m: m = ALICE.m if not force and not click.confirm(f'Use default value for M ({m})?', default=True): m = click.prompt('Enter threshold (M)', type=click.IntRange(1, n)) # Policy Value policy_value_provided = bool(value) or bool(rate) if not ALICE.federated_only and not policy_value_provided: rate = ALICE.default_rate # TODO #1709 - Fine tuning and selection of default rates if not force: default_gwei = Web3.fromWei(rate, 'gwei') prompt = "Confirm rate of {node_rate} gwei ({total_rate} gwei per period)?" if not click.confirm(prompt.format(node_rate=default_gwei, total_rate=default_gwei*n), default=True): interactive_rate = click.prompt('Enter rate per period in gwei', type=GWEI) # TODO: Validate interactively collected rate (#1709) click.confirm(prompt.format(node_rate=rate, total_rate=rate*n), default=True, abort=True) rate = Web3.toWei(interactive_rate, 'gwei') # Request grant_request = { 'bob_encrypting_key': bob_encrypting_key, 'bob_verifying_key': bob_verifying_key, 'label': label, 'm': m, 'n': n, 'expiration': expiration, } if not ALICE.federated_only: if value: grant_request['value'] = value elif rate: grant_request['rate'] = rate # in wei if not force and not general_config.json_ipc: confirm_staged_grant(emitter=emitter, grant_request=grant_request) emitter.echo(f'Granting Access to {bob_verifying_key[:8]}', color='yellow') return ALICE.controller.grant(request=grant_request)
def prolong(general_config, transacting_staker_options, config_file, force, lock_periods, index): """Prolong an existing stake's duration.""" # Setup emitter = setup_emitter(general_config) STAKEHOLDER = transacting_staker_options.create_character( emitter, config_file) action_period = STAKEHOLDER.staking_agent.get_current_period() blockchain = transacting_staker_options.get_blockchain() economics = STAKEHOLDER.economics # Handle account selection client_account, staking_address = select_client_account_for_staking( emitter=emitter, stakeholder=STAKEHOLDER, staking_address=transacting_staker_options.staker_options. staking_address, individual_allocation=STAKEHOLDER.individual_allocation, force=force) # Handle stake update and selection if transacting_staker_options.staker_options.staking_address and index is not None: # 0 is valid. STAKEHOLDER.stakes = StakeList( registry=STAKEHOLDER.registry, checksum_address=transacting_staker_options.staker_options. staking_address) STAKEHOLDER.stakes.refresh() current_stake = STAKEHOLDER.stakes[index] else: current_stake = select_stake(stakeholder=STAKEHOLDER, emitter=emitter) # # Prolong # # Interactive if not lock_periods: max_extension = MAX_UINT16 - current_stake.final_locked_period # +1 because current period excluded min_extension = economics.minimum_locked_periods - current_stake.periods_remaining + 1 if min_extension < 1: min_extension = 1 duration_extension_range = click.IntRange(min=min_extension, max=max_extension, clamp=False) lock_periods = click.prompt(PROMPT_PROLONG_VALUE.format( minimum=min_extension, maximum=max_extension), type=duration_extension_range) if not force: click.confirm(CONFIRM_PROLONG.format(lock_periods=lock_periods), abort=True) password = transacting_staker_options.get_password(blockchain, client_account) # Non-interactive: Consistency check to prevent the above agreement from going stale. last_second_current_period = STAKEHOLDER.staking_agent.get_current_period() if action_period != last_second_current_period: emitter.echo(PERIOD_ADVANCED_WARNING, red='red') raise click.Abort # Authenticate and Execute STAKEHOLDER.assimilate(checksum_address=current_stake.staker_address, password=password) receipt = STAKEHOLDER.prolong_stake(stake_index=current_stake.index, additional_periods=lock_periods) # Report emitter.echo(SUCCESSFUL_STAKE_PROLONG, color='green', verbosity=1) paint_receipt_summary(emitter=emitter, receipt=receipt, chain_name=blockchain.client.chain_name) paint_stakes(emitter=emitter, stakeholder=STAKEHOLDER)
def create(general_config, transacting_staker_options, config_file, force, value, lock_periods): """Initialize a new stake.""" # Setup emitter = setup_emitter(general_config) STAKEHOLDER = transacting_staker_options.create_character( emitter, config_file) blockchain = transacting_staker_options.get_blockchain() economics = STAKEHOLDER.economics client_account, staking_address = select_client_account_for_staking( emitter=emitter, stakeholder=STAKEHOLDER, staking_address=transacting_staker_options.staker_options. staking_address, individual_allocation=STAKEHOLDER.individual_allocation, force=force) # 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) # # Stage Stake # if not value: token_balance = NU.from_nunits( STAKEHOLDER.token_agent.get_balance(staking_address)) lower_limit = NU.from_nunits( STAKEHOLDER.economics.minimum_allowed_locked) upper_limit = min( token_balance, NU.from_nunits(STAKEHOLDER.economics.maximum_allowed_locked)) value = click.prompt( f"Enter stake value in NU " f"({lower_limit} - {upper_limit})", type=stake_value_range, default=upper_limit.to_tokens()) value = NU.from_tokens(value) if not lock_periods: min_locktime = STAKEHOLDER.economics.minimum_locked_periods default_locktime = STAKEHOLDER.economics.maximum_rewarded_periods max_locktime = MAX_UINT16 - STAKEHOLDER.staking_agent.get_current_period( ) prompt = f"Enter stake duration ({min_locktime} - {max_locktime})" lock_periods = click.prompt(prompt, type=stake_duration_range, default=default_locktime) start_period = STAKEHOLDER.staking_agent.get_current_period() + 1 unlock_period = start_period + lock_periods # # Review and Publish # if not force: confirm_large_stake(value=value, lock_periods=lock_periods) paint_staged_stake(emitter=emitter, stakeholder=STAKEHOLDER, staking_address=staking_address, stake_value=value, lock_periods=lock_periods, start_period=start_period, unlock_period=unlock_period) confirm_staged_stake(staker_address=staking_address, value=value, lock_periods=lock_periods) # Last chance to bail click.confirm(CONFIRM_BROADCAST_CREATE_STAKE, abort=True) # Authenticate password = transacting_staker_options.get_password(blockchain, client_account) # Consistency check to prevent the above agreement from going stale. last_second_current_period = STAKEHOLDER.staking_agent.get_current_period() if start_period != last_second_current_period + 1: emitter.echo(PERIOD_ADVANCED_WARNING, color='red') raise click.Abort # Authenticate and Execute STAKEHOLDER.assimilate(checksum_address=client_account, password=password) new_stake = STAKEHOLDER.initialize_stake(amount=value, lock_periods=lock_periods) paint_staking_confirmation(emitter=emitter, staker=STAKEHOLDER, new_stake=new_stake)
def list_stakes(general_config, staker_options, config_file, all): """List active stakes for current stakeholder.""" emitter = setup_emitter(general_config) STAKEHOLDER = staker_options.create_character(emitter, config_file) paint_stakes(emitter=emitter, stakeholder=STAKEHOLDER, paint_inactive=all)
def destroy(general_config, config_options, config_file, force): """Delete existing Alice's configuration.""" emitter = setup_emitter(general_config) alice_config = config_options.create_config(emitter, config_file) destroy_configuration(emitter, character_config=alice_config, force=force)
def revoke(general_config, bob_verifying_key, label, character_options, config_file): """Revoke a policy.""" emitter = setup_emitter(general_config) ALICE = character_options.create_character(emitter, config_file, general_config.json_ipc) revoke_request = {'label': label, 'bob_verifying_key': bob_verifying_key} return ALICE.controller.revoke(request=revoke_request)
def destroy(general_config, config_options, config_file, force): """Destroy Felix Configuration.""" emitter = setup_emitter(general_config, config_options.checksum_address) felix_config = config_options.create_config(emitter, config_file) destroy_configuration(emitter, character_config=felix_config, force=force)
def increase(general_config, transacting_staker_options, config_file, force, value, index, only_lock): """Increase an existing stake.""" # Setup emitter = setup_emitter(general_config) STAKEHOLDER = transacting_staker_options.create_character(emitter, config_file) blockchain = transacting_staker_options.get_blockchain() client_account, staking_address = select_client_account_for_staking( emitter=emitter, stakeholder=STAKEHOLDER, staking_address=transacting_staker_options.staker_options.staking_address, individual_allocation=STAKEHOLDER.individual_allocation, force=force) # Handle stake update and selection if index is not None: # 0 is valid. current_stake = STAKEHOLDER.stakes[index] else: current_stake = select_stake(staker=STAKEHOLDER, emitter=emitter) # # Stage Stake # if not value: if not only_lock: only_lock = not click.prompt(PROMPT_DEPOSIT_OR_LOCK, type=click.BOOL, default=True) token_balance = STAKEHOLDER.token_balance if not only_lock else STAKEHOLDER.calculate_staking_reward() locked_tokens = STAKEHOLDER.locked_tokens(periods=1).to_nunits() upper_limit = min(token_balance, NU.from_nunits(STAKEHOLDER.economics.maximum_allowed_locked - locked_tokens)) if token_balance == 0: emitter.echo(INSUFFICIENT_BALANCE_TO_INCREASE, color='red') raise click.Abort if upper_limit == 0: emitter.echo(MAXIMUM_STAKE_REACHED, color='red') raise click.Abort stake_value_range = click.FloatRange(min=0, max=upper_limit.to_tokens(), clamp=False) value = click.prompt(PROMPT_STAKE_INCREASE_VALUE.format(upper_limit=upper_limit), type=stake_value_range) value = NU.from_tokens(value) # # Review and Publish # if not force: lock_periods = current_stake.periods_remaining - 1 current_period = STAKEHOLDER.staking_agent.get_current_period() unlock_period = current_stake.final_locked_period + 1 confirm_large_stake(value=value, lock_periods=lock_periods) paint_staged_stake(emitter=emitter, stakeholder=STAKEHOLDER, staking_address=staking_address, stake_value=value, lock_periods=lock_periods, start_period=current_period + 1, unlock_period=unlock_period) click.confirm(CONFIRM_INCREASING_STAKE.format(stake_index=current_stake.index, value=value), abort=True) # Authenticate password = transacting_staker_options.get_password(blockchain, client_account) STAKEHOLDER.assimilate(password=password) # Execute receipt = STAKEHOLDER.increase_stake(stake=current_stake, amount=value, only_lock=only_lock) # Report emitter.echo(SUCCESSFUL_STAKE_INCREASE, color='green', verbosity=1) paint_receipt_summary(emitter=emitter, receipt=receipt, chain_name=blockchain.client.chain_name) paint_stakes(emitter=emitter, staker=STAKEHOLDER)
def create(general_config, transacting_staker_options, config_file, force, value, lock_periods, only_lock): """Initialize a new stake.""" # Setup emitter = setup_emitter(general_config) STAKEHOLDER = transacting_staker_options.create_character(emitter, config_file) blockchain = transacting_staker_options.get_blockchain() economics = STAKEHOLDER.economics client_account, staking_address = select_client_account_for_staking( emitter=emitter, stakeholder=STAKEHOLDER, staking_address=transacting_staker_options.staker_options.staking_address, individual_allocation=STAKEHOLDER.individual_allocation, force=force) # 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) # # Stage Stake # if not value: if not only_lock: only_lock = not click.prompt(PROMPT_DEPOSIT_OR_LOCK, type=click.BOOL, default=True) token_balance = STAKEHOLDER.token_balance if not only_lock else STAKEHOLDER.calculate_staking_reward() lower_limit = NU.from_nunits(STAKEHOLDER.economics.minimum_allowed_locked) locked_tokens = STAKEHOLDER.locked_tokens(periods=1).to_nunits() upper_limit = min(token_balance, NU.from_nunits(STAKEHOLDER.economics.maximum_allowed_locked - locked_tokens)) if token_balance <= lower_limit: emitter.echo(INSUFFICIENT_BALANCE_TO_CREATE, color='red') raise click.Abort if upper_limit <= lower_limit: emitter.echo(MAXIMUM_STAKE_REACHED, color='red') raise click.Abort value = click.prompt(PROMPT_STAKE_CREATE_VALUE.format(lower_limit=lower_limit, upper_limit=upper_limit), type=stake_value_range, default=upper_limit.to_tokens()) value = NU.from_tokens(value) if not lock_periods: min_locktime = STAKEHOLDER.economics.minimum_locked_periods default_locktime = STAKEHOLDER.economics.maximum_rewarded_periods max_locktime = MAX_UINT16 - STAKEHOLDER.staking_agent.get_current_period() lock_periods = click.prompt(PROMPT_STAKE_CREATE_LOCK_PERIODS.format(min_locktime=min_locktime, max_locktime=max_locktime), type=stake_duration_range, default=default_locktime) start_period = STAKEHOLDER.staking_agent.get_current_period() + 1 unlock_period = start_period + lock_periods # # Review and Publish # if not force: confirm_large_stake(value=value, lock_periods=lock_periods) paint_staged_stake(emitter=emitter, stakeholder=STAKEHOLDER, staking_address=staking_address, stake_value=value, lock_periods=lock_periods, start_period=start_period, unlock_period=unlock_period) confirm_staged_stake(staker_address=staking_address, value=value, lock_periods=lock_periods) # Last chance to bail click.confirm(CONFIRM_BROADCAST_CREATE_STAKE, abort=True) # Authenticate password = transacting_staker_options.get_password(blockchain, client_account) STAKEHOLDER.assimilate(password=password) # Consistency check to prevent the above agreement from going stale. last_second_current_period = STAKEHOLDER.staking_agent.get_current_period() if start_period != last_second_current_period + 1: emitter.echo(PERIOD_ADVANCED_WARNING, color='red') raise click.Abort # Execute receipt = STAKEHOLDER.initialize_stake(amount=value, lock_periods=lock_periods, only_lock=only_lock) paint_staking_confirmation(emitter=emitter, staker=STAKEHOLDER, receipt=receipt)
def accounts(general_config, staker_options, config_file): """Show ETH and NU balances for stakeholder's accounts.""" emitter = setup_emitter(general_config) STAKEHOLDER = staker_options.create_character(emitter, config_file) paint_staking_accounts(emitter=emitter, wallet=STAKEHOLDER.wallet, registry=STAKEHOLDER.registry)
def init_stakeholder(general_config, config_root, force, config_options): """Create a new stakeholder configuration.""" emitter = setup_emitter(general_config) new_stakeholder = config_options.generate_config(config_root) filepath = new_stakeholder.to_configuration_file(override=force) emitter.echo(SUCCESSFUL_NEW_STAKEHOLDER_CONFIG.format(filepath=filepath), color='green')
def run(general_config, network, provider_uri, federated_only, teacher_uri, registry_filepath, min_stake, http_port, tls_certificate_filepath, tls_key_filepath, basic_auth_filepath, allow_origins, dry_run, eager): """Start Porter's Web controller.""" emitter = setup_emitter(general_config, banner=Porter.BANNER) # HTTP/HTTPS if bool(tls_key_filepath) ^ bool(tls_certificate_filepath): raise click.BadOptionUsage( option_name='--tls-key-filepath, --tls-certificate-filepath', message=PORTER_BOTH_TLS_KEY_AND_CERTIFICATION_MUST_BE_PROVIDED) is_https = (tls_key_filepath and tls_certificate_filepath) # check authentication if basic_auth_filepath and not is_https: raise click.BadOptionUsage(option_name='--basic-auth-filepath', message=PORTER_BASIC_AUTH_REQUIRES_HTTPS) if federated_only: if not teacher_uri: raise click.BadOptionUsage( option_name='--teacher', message="--teacher is required for federated porter.") teacher = Ursula.from_teacher_uri( teacher_uri=teacher_uri, federated_only=True, min_stake=min_stake) # min stake is irrelevant for federated PORTER = Porter(domain=TEMPORARY_DOMAIN, start_learning_now=eager, known_nodes={teacher}, verify_node_bonding=False, federated_only=True) else: # decentralized/blockchain if not provider_uri: raise click.BadOptionUsage( option_name='--provider', message="--provider is required for decentralized porter.") if not network: # should never happen - network defaults to 'mainnet' if not specified raise click.BadOptionUsage( option_name='--network', message="--network is required for decentralized porter.") registry = get_registry(network=network, registry_filepath=registry_filepath) teacher = None if teacher_uri: teacher = Ursula.from_teacher_uri( teacher_uri=teacher_uri, federated_only=False, # always False min_stake=min_stake, registry=registry) PORTER = Porter(domain=network, known_nodes={teacher} if teacher else None, registry=registry, start_learning_now=eager, provider_uri=provider_uri) # RPC if general_config.json_ipc: rpc_controller = PORTER.make_rpc_controller() _transport = rpc_controller.make_control_transport() rpc_controller.start() return emitter.message(f"Network: {PORTER.domain.capitalize()}", color='green') if not federated_only: emitter.message(f"Provider: {provider_uri}", color='green') # firm up falsy status (i.e. change specified empty string to None) allow_origins = allow_origins if allow_origins else None # covert to list of strings/regexes allow_origins_list = None if allow_origins: allow_origins_list = allow_origins.split( ",") # split into list of origins to allow emitter.message(PORTER_CORS_ALLOWED_ORIGINS.format( allow_origins=allow_origins_list), color='green') if basic_auth_filepath: emitter.message(PORTER_BASIC_AUTH_ENABLED, color='green') controller = PORTER.make_web_controller( crash_on_error=False, htpasswd_filepath=basic_auth_filepath, cors_allow_origins_list=allow_origins_list) http_scheme = "https" if is_https else "http" message = PORTER_RUN_MESSAGE.format(http_scheme=http_scheme, http_port=http_port) emitter.message(message, color='green', bold=True) return controller.start(port=http_port, tls_key_filepath=tls_key_filepath, tls_certificate_filepath=tls_certificate_filepath, dry_run=dry_run)
def public_keys(general_config, character_options, config_file): """Obtain Alice's public verification and encryption keys.""" emitter = setup_emitter(general_config) ALICE = character_options.create_character(emitter, config_file, general_config.json_ipc, load_seednodes=False) response = ALICE.controller.public_keys() return response
def derive_policy_pubkey(general_config, label, character_options, config_file): """Get a policy public key from a policy label.""" emitter = setup_emitter(general_config) ALICE = character_options.create_character(emitter, config_file, general_config.json_ipc, load_seednodes=False) return ALICE.controller.derive_policy_encrypting_key(label=label)
def restake(general_config, transacting_staker_options, config_file, enable, lock_until, force): """Manage re-staking with --enable or --disable.""" # Setup emitter = setup_emitter(general_config) STAKEHOLDER = transacting_staker_options.create_character( emitter, config_file) blockchain = transacting_staker_options.get_blockchain() client_account, staking_address = select_client_account_for_staking( emitter=emitter, stakeholder=STAKEHOLDER, staking_address=transacting_staker_options.staker_options. staking_address, individual_allocation=STAKEHOLDER.individual_allocation, force=force) # Inner Exclusive Switch if lock_until: if not force: confirm_enable_restaking_lock(emitter, staking_address=staking_address, release_period=lock_until) # Authenticate and Execute password = transacting_staker_options.get_password( blockchain, client_account) STAKEHOLDER.assimilate(password=password) receipt = STAKEHOLDER.enable_restaking_lock(release_period=lock_until) emitter.echo(SUCCESSFUL_ENABLE_RESTAKE_LOCK.format( staking_address=staking_address, lock_until=lock_until), color='green', verbosity=1) elif enable: if not force: confirm_enable_restaking(emitter, staking_address=staking_address) # Authenticate and Execute password = transacting_staker_options.get_password( blockchain, client_account) STAKEHOLDER.assimilate(password=password) receipt = STAKEHOLDER.enable_restaking() emitter.echo(SUCCESSFUL_ENABLE_RESTAKING.format( staking_address=staking_address), color='green', verbosity=1) else: if not force: click.confirm(CONFIRM_DISABLE_RESTAKING.format( staking_address=staking_address), abort=True) # Authenticate and Execute password = transacting_staker_options.get_password( blockchain, client_account) STAKEHOLDER.assimilate(password=password) receipt = STAKEHOLDER.disable_restaking() emitter.echo(SUCCESSFUL_DISABLE_RESTAKING.format( staking_address=staking_address), color='green', verbosity=1) paint_receipt_summary(receipt=receipt, emitter=emitter, chain_name=blockchain.client.chain_name)
def forget(general_config, config_options, config_file): """Forget all known nodes.""" emitter = setup_emitter(general_config, config_options.worker_address) _pre_launch_warnings(emitter, dev=config_options.dev, force=None) ursula_config = config_options.create_config(emitter, config_file) forget_nodes(emitter, configuration=ursula_config)
def collect_reward(general_config, transacting_staker_options, config_file, staking_reward, policy_fee, withdraw_address, force): """Withdraw staking reward.""" # Setup emitter = setup_emitter(general_config) STAKEHOLDER = transacting_staker_options.create_character( emitter, config_file) blockchain = transacting_staker_options.get_blockchain() if not staking_reward and not policy_fee: raise click.BadArgumentUsage( f"Either --staking-reward or --policy-fee must be True to collect rewards." ) client_account, staking_address = select_client_account_for_staking( emitter=emitter, stakeholder=STAKEHOLDER, staking_address=transacting_staker_options.staker_options. staking_address, individual_allocation=STAKEHOLDER.individual_allocation, force=force) password = None if staking_reward: # Note: Sending staking / inflation rewards to another account is not allowed. reward_amount = NU.from_nunits(STAKEHOLDER.calculate_staking_reward()) if reward_amount == 0: emitter.echo(NO_TOKENS_TO_WITHDRAW, color='red') raise click.Abort emitter.echo(message=COLLECTING_TOKEN_REWARD.format( reward_amount=reward_amount)) withdrawing_last_portion = STAKEHOLDER.non_withdrawable_stake() == 0 if not force and withdrawing_last_portion and STAKEHOLDER.mintable_periods( ) > 0: click.confirm(CONFIRM_COLLECTING_WITHOUT_MINTING, abort=True) # Authenticate and Execute password = transacting_staker_options.get_password( blockchain, client_account) STAKEHOLDER.assimilate(password=password) staking_receipt = STAKEHOLDER.collect_staking_reward() paint_receipt_summary( receipt=staking_receipt, chain_name=STAKEHOLDER.wallet.blockchain.client.chain_name, emitter=emitter) if policy_fee: fee_amount = Web3.fromWei(STAKEHOLDER.calculate_policy_fee(), 'ether') if fee_amount == 0: emitter.echo(NO_FEE_TO_WITHDRAW, color='red') raise click.Abort emitter.echo(message=COLLECTING_ETH_FEE.format(fee_amount=fee_amount)) if password is None: # Authenticate and Execute password = transacting_staker_options.get_password( blockchain, client_account) STAKEHOLDER.assimilate(password=password) policy_receipt = STAKEHOLDER.collect_policy_fee( collector_address=withdraw_address) paint_receipt_summary( receipt=policy_receipt, chain_name=STAKEHOLDER.wallet.blockchain.client.chain_name, emitter=emitter)
def grant(general_config, bob, bob_encrypting_key, bob_verifying_key, label, value, rate, expiration, threshold, shares, character_options, config_file, force): """Create and enact an access policy for Bob.""" # Setup emitter = setup_emitter(general_config) ALICE = character_options.create_character( emitter=emitter, config_file=config_file, json_ipc=general_config.json_ipc) validate_grant_command(emitter=emitter, alice=ALICE, force=force, bob=bob, label=label, rate=rate, value=value, expiration=expiration, bob_encrypting_key=bob_encrypting_key, bob_verifying_key=bob_verifying_key) # Collect bob_public_keys = collect_bob_public_keys( emitter=emitter, force=force, card_identifier=bob, bob_encrypting_key=bob_encrypting_key, bob_verifying_key=bob_verifying_key) policy = collect_policy_parameters( emitter=emitter, alice=ALICE, force=force, bob_identifier=bob_public_keys.verifying_key[:8], label=label, threshold=threshold, shares=shares, rate=rate, value=value, expiration=expiration) grant_request = { 'bob_encrypting_key': bob_public_keys.encrypting_key, 'bob_verifying_key': bob_public_keys.verifying_key, 'label': policy.label, 'threshold': policy.threshold, 'shares': policy.shares, 'expiration': policy.expiration, } if not ALICE.federated_only: # These values can be 0 if policy.value is not None: grant_request['value'] = policy.value elif policy.rate is not None: grant_request['rate'] = policy.rate # in wei # Grant if not force and not general_config.json_ipc: confirm_staged_grant( emitter=emitter, grant_request=grant_request, federated=ALICE.federated_only, seconds_per_period=(None if ALICE.federated_only else ALICE.economics.seconds_per_period)) emitter.echo(f'Granting Access to {bob_public_keys.verifying_key[:8]}', color='yellow') return ALICE.controller.grant(request=grant_request)
def recover(general_config, config_options): # TODO: Combine with work in PR #2682 # TODO: Integrate regeneration of configuration files emitter = setup_emitter(general_config, config_options.worker_address) recover_keystore(emitter=emitter)
def bond_worker(general_config, transacting_staker_options, config_file, force, worker_address): """Bond a worker to a staker.""" emitter = setup_emitter(general_config) STAKEHOLDER = transacting_staker_options.create_character( emitter, config_file) blockchain = transacting_staker_options.get_blockchain() economics = STAKEHOLDER.economics client_account, staking_address = select_client_account_for_staking( emitter=emitter, stakeholder=STAKEHOLDER, staking_address=transacting_staker_options.staker_options. staking_address, individual_allocation=STAKEHOLDER.individual_allocation, force=force) if not worker_address: worker_address = click.prompt(PROMPT_WORKER_ADDRESS, type=EIP55_CHECKSUM_ADDRESS) if (worker_address == staking_address) and not force: click.confirm(CONFIRM_WORKER_AND_STAKER_ADDRESSES_ARE_EQUAL.format( address=worker_address), abort=True) # TODO: Check preconditions (e.g., minWorkerPeriods, already in use, etc) password = transacting_staker_options.get_password(blockchain, client_account) # TODO: Double-check dates # Calculate release datetime 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.economics.minimum_worker_periods release_period = current_period + min_worker_periods release_date = datetime_at_period( period=release_period, seconds_per_period=economics.seconds_per_period, start_of_period=True) if not force: click.confirm( f"Commit to bonding " f"worker {worker_address} to staker {staking_address} " f"for a minimum of {STAKEHOLDER.economics.minimum_worker_periods} periods?", abort=True) STAKEHOLDER.assimilate(checksum_address=client_account, password=password) receipt = STAKEHOLDER.bond_worker(worker_address=worker_address) # Report Success message = SUCCESSFUL_WORKER_BONDING.format(worker_address=worker_address, staking_address=staking_address) emitter.echo(message, color='green') paint_receipt_summary(emitter=emitter, receipt=receipt, chain_name=blockchain.client.chain_name, transaction_type='bond_worker') emitter.echo(BONDING_DETAILS.format(current_period=current_period, bonded_date=bonded_date), color='green') emitter.echo(BONDING_RELEASE_INFO.format(release_period=release_period, release_date=release_date), color='green')
def setup(self, general_config) -> tuple: emitter = setup_emitter(general_config) # TODO: Restore Banner: network=self.network.capitalize() registry = get_registry(network=self.network, registry_filepath=self.registry_filepath) blockchain = connect_to_blockchain(emitter=emitter, provider_uri=self.provider_uri) return emitter, registry, blockchain
def divide(general_config, transacting_staker_options, config_file, force, value, lock_periods, index): """Create a new stake from part of an existing one.""" # Setup emitter = setup_emitter(general_config) STAKEHOLDER = transacting_staker_options.create_character( emitter, config_file) blockchain = transacting_staker_options.get_blockchain() economics = STAKEHOLDER.economics action_period = STAKEHOLDER.staking_agent.get_current_period() client_account, staking_address = select_client_account_for_staking( emitter=emitter, stakeholder=STAKEHOLDER, staking_address=transacting_staker_options.staker_options. staking_address, individual_allocation=STAKEHOLDER.individual_allocation, force=force) # 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) if transacting_staker_options.staker_options.staking_address and index is not None: # 0 is valid. STAKEHOLDER.stakes = StakeList( registry=STAKEHOLDER.registry, checksum_address=transacting_staker_options.staker_options. staking_address) STAKEHOLDER.stakes.refresh() current_stake = STAKEHOLDER.stakes[index] else: current_stake = select_stake(stakeholder=STAKEHOLDER, emitter=emitter, divisible=True, staker_address=client_account) # # Stage Stake # # Value if not value: min_allowed_locked = NU.from_nunits( STAKEHOLDER.economics.minimum_allowed_locked) max_divide_value = max(min_allowed_locked, current_stake.value - min_allowed_locked) prompt = PROMPT_STAKE_DIVIDE_VALUE.format( minimm=min_allowed_locked, maximum=str(max_divide_value)) value = click.prompt(prompt, type=stake_value_range) value = NU(value, 'NU') # Duration if not lock_periods: max_extension = MAX_UINT16 - current_stake.final_locked_period divide_extension_range = click.IntRange(min=1, max=max_extension, clamp=False) extension = click.prompt(PROMPT_STAKE_EXTEND_VALUE, type=divide_extension_range) else: extension = lock_periods if not force: confirm_large_stake(lock_periods=extension, value=value) paint_staged_stake_division(emitter=emitter, stakeholder=STAKEHOLDER, original_stake=current_stake, target_value=value, extension=extension) click.confirm(CONFIRM_BROADCAST_STAKE_DIVIDE, abort=True) # Authenticate password = transacting_staker_options.get_password(blockchain, client_account) # Consistency check to prevent the above agreement from going stale. last_second_current_period = STAKEHOLDER.staking_agent.get_current_period() if action_period != last_second_current_period: emitter.echo(PERIOD_ADVANCED_WARNING, red='red') raise click.Abort # Execute 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(SUCCESSFUL_STAKE_DIVIDE, color='green', verbosity=1) paint_receipt_summary(emitter=emitter, receipt=new_stake.receipt, chain_name=blockchain.client.chain_name) # Show the resulting stake list paint_stakes(emitter=emitter, stakeholder=STAKEHOLDER)
def destroy(general_config, config_options, config_file, force): """Delete Ursula node configuration.""" emitter = setup_emitter(general_config, config_options.worker_address) _pre_launch_warnings(emitter, dev=config_options.dev, force=force) ursula_config = config_options.create_config(emitter, config_file) destroy_configuration(emitter, character_config=ursula_config, force=force)
def retrieve_and_decrypt(general_config, character_options, config_file, alice_verifying_key, treasure_map, message_kit, ipfs, alice, decode, force): """Obtain plaintext from encrypted data, if access was granted.""" # 'message_kit' is a required and a "multiple" value click option - the option name was kept singular so that # it makes sense when specifying many of them i.e. `--message-kit <message_kit_1> --message-kit <message_kit_2> ...` message_kits = list(message_kit) # Setup emitter = setup_emitter(general_config) BOB = character_options.create_character(emitter, config_file, json_ipc=general_config.json_ipc) if not (bool(alice_verifying_key) ^ bool(alice)): message = f"Pass either '--alice_verifying_key' or '--alice'; " \ f"got {'both' if alice_verifying_key else 'neither'}" raise click.BadOptionUsage( option_name='--alice_verifying_key, --alice', message=message) if not alice_verifying_key: if alice: # from storage card = Card.load(identifier=alice) if card.character is not Alice: emitter.error('Grantee card is not an Alice.') raise click.Abort alice_verifying_key = bytes(card.verifying_key).hex() emitter.message( f'{card.nickname or ("Alice #"+card.id.hex())}\n' f'Verifying Key | {bytes(card.verifying_key).hex()}', color='green') if not force: click.confirm('Is this the correct Granter (Alice)?', abort=True) if ipfs: # '--message_kit' option was repurposed to specify ipfs cids (#2098) cids = [] for cid in message_kits: cids.append(cid) # populate message_kits list with actual message_kits message_kits = [] import ipfshttpclient # TODO: #2108 emitter.message(f"Connecting to IPFS Gateway {ipfs}") ipfs_client = ipfshttpclient.connect(ipfs) for cid in cids: raw_message_kit = ipfs_client.cat( cid) # cat the contents at the hash reference emitter.message(f"Downloaded message kit from IPFS (CID {cid})", color='green') message_kit = raw_message_kit.decode() # cast to utf-8 message_kits.append(message_kit) # Request bob_request_data = { 'alice_verifying_key': alice_verifying_key, 'message_kits': message_kits, 'encrypted_treasure_map': treasure_map } response = BOB.controller.retrieve_and_decrypt(request=bob_request_data) if decode: messages = list( [b64decode(r).decode() for r in response['cleartexts']]) emitter.echo('----------Messages----------') for message in messages: emitter.echo(message) return response
def public_keys(general_config, character_options, config_file): """Obtain Bob's public verification and encryption keys.""" emitter = setup_emitter(general_config) BOB = character_options.create_character(emitter, config_file) response = BOB.controller.public_keys() return response