mirror of https://github.com/nucypher/nucypher.git
Create a console emitter only once (in NucypherClickConfig) and propagate it everywhere
parent
52dbca6519
commit
0ce9458bf0
|
@ -318,7 +318,7 @@ class JSONRPCController(CharacterControlServer):
|
|||
|
||||
if not control_requests:
|
||||
e = self.emitter.InvalidRequest()
|
||||
return self.emitter(e=e)
|
||||
return self.emitter.error(e)
|
||||
|
||||
batch_size = 0
|
||||
for request in control_requests: # TODO: parallelism
|
||||
|
@ -332,7 +332,7 @@ class JSONRPCController(CharacterControlServer):
|
|||
control_request = json.loads(control_request)
|
||||
except JSONDecodeError:
|
||||
e = self.emitter.ParseError()
|
||||
return self.emitter(e=e)
|
||||
return self.emitter.error(e)
|
||||
|
||||
# Handle batch of messages
|
||||
if isinstance(control_request, list):
|
||||
|
@ -343,12 +343,12 @@ class JSONRPCController(CharacterControlServer):
|
|||
return self.handle_message(message=control_request, *args, **kwargs)
|
||||
|
||||
except self.emitter.JSONRPCError as e:
|
||||
return self.emitter(e=e)
|
||||
return self.emitter.error(e)
|
||||
|
||||
except Exception as e:
|
||||
if self.crash_on_error:
|
||||
raise
|
||||
return self.emitter(e=e)
|
||||
return self.emitter.error(e)
|
||||
|
||||
|
||||
class WebController(CharacterControlServer):
|
||||
|
|
|
@ -13,81 +13,63 @@ class StdoutEmitter:
|
|||
|
||||
transport_serializer = str
|
||||
default_color = 'white'
|
||||
default_sink_callable = sys.stdout.write
|
||||
|
||||
__stdout_trap = list()
|
||||
# sys.stdout.write() doesn't work well with click_runner's output capture
|
||||
default_sink_callable = print
|
||||
|
||||
def __init__(self,
|
||||
sink: Callable = None,
|
||||
capture_stdout: bool = False,
|
||||
quiet: bool = False):
|
||||
|
||||
self.name = self.__class__.__name__.lower()
|
||||
self.sink = sink or self.default_sink_callable
|
||||
self.capture_stdout = capture_stdout
|
||||
self.quiet = quiet
|
||||
self.log = Logger(self.name)
|
||||
|
||||
super().__init__()
|
||||
def clear(self):
|
||||
if not self.quiet:
|
||||
click.clear()
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
try:
|
||||
return self._emit(*args, **kwargs)
|
||||
except Exception:
|
||||
self.log.debug("Error while emitting nucypher controller output")
|
||||
raise
|
||||
|
||||
def trap_output(self, output) -> int:
|
||||
self.__stdout_trap.append(output)
|
||||
return len(bytes(output)) # number of bytes written
|
||||
|
||||
def _emit(self,
|
||||
response: dict = None,
|
||||
message: str = None,
|
||||
color: str = None,
|
||||
bold: bool = False,
|
||||
) -> None:
|
||||
|
||||
"""
|
||||
Write pretty messages to stdout. For Human consumption only.
|
||||
"""
|
||||
if response and message:
|
||||
raise ValueError(f'{self.__class__.__name__} received both a response and a message.')
|
||||
|
||||
if self.quiet:
|
||||
# reduces the number of CLI conditionals by
|
||||
# wrapping console output functions
|
||||
return
|
||||
|
||||
if self.capture_stdout:
|
||||
self.trap_output(response or message)
|
||||
|
||||
elif response:
|
||||
# WARNING: Do not log in this block
|
||||
for k, v in response.items():
|
||||
click.secho(message=f'{k} ...... {v}',
|
||||
fg=color or self.default_color,
|
||||
bold=bold)
|
||||
|
||||
elif message:
|
||||
# Most likely a message emitted without a character control instance
|
||||
click.secho(message=message, fg=color or self.default_color, bold=bold)
|
||||
def message(self,
|
||||
message: str = None,
|
||||
color: str = None,
|
||||
bold: bool = False):
|
||||
if not self.quiet:
|
||||
self.echo(message=message, color=color or self.default_color, bold=bold)
|
||||
self.log.debug(message)
|
||||
|
||||
else:
|
||||
raise ValueError('Either "response" dict or "message" str is required, but got neither.')
|
||||
def echo(self,
|
||||
message: str = None,
|
||||
color: str = None,
|
||||
bold: bool = False,
|
||||
nl: bool = True):
|
||||
if not self.quiet:
|
||||
click.secho(message=message, fg=color or self.default_color, bold=bold, nl=nl)
|
||||
|
||||
def banner(self, banner):
|
||||
if not self.quiet:
|
||||
click.echo(banner)
|
||||
|
||||
def ipc(self, response: dict, request_id: int, duration):
|
||||
# WARNING: Do not log in this block
|
||||
if not self.quiet:
|
||||
for k, v in response.items():
|
||||
click.secho(message=f'{k} ...... {v}', fg=self.default_color)
|
||||
|
||||
def error(self, e):
|
||||
if not self.quiet:
|
||||
e_str = str(e)
|
||||
click.echo(message=e_str)
|
||||
self.log.info(e_str)
|
||||
|
||||
|
||||
class JSONRPCStdoutEmitter(StdoutEmitter):
|
||||
|
||||
transport_serializer = json.dumps
|
||||
default_sink_callable = print
|
||||
delimiter = '\n'
|
||||
|
||||
def __init__(self, sink: Callable = None, *args, **kwargs):
|
||||
self.sink = sink or self.default_sink_callable
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.log = Logger("JSON-RPC-Emitter")
|
||||
|
||||
class JSONRPCError(RuntimeError):
|
||||
|
@ -114,17 +96,6 @@ class JSONRPCStdoutEmitter(StdoutEmitter):
|
|||
code = -32603
|
||||
message = "Internal JSON-RPC error."
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
if 'response' in kwargs:
|
||||
return self.__emit_rpc_response(*args, **kwargs)
|
||||
elif 'message' in kwargs:
|
||||
if not self.quiet:
|
||||
self.log.info(*args, **kwargs)
|
||||
elif 'e' in kwargs:
|
||||
return self.__emit_rpc_error(*args, **kwargs)
|
||||
else:
|
||||
raise self.JSONRPCError("Internal Error")
|
||||
|
||||
@staticmethod
|
||||
def assemble_response(response: dict, message_id: int) -> dict:
|
||||
response_data = {'jsonrpc': '2.0',
|
||||
|
@ -162,16 +133,39 @@ class JSONRPCStdoutEmitter(StdoutEmitter):
|
|||
|
||||
serialized_response = self.__serialize(data=data)
|
||||
|
||||
# Capture Message Output
|
||||
if self.capture_stdout:
|
||||
return self.trap_output(serialized_response)
|
||||
|
||||
# Write to stdout file descriptor
|
||||
else:
|
||||
number_of_written_bytes = self.sink(serialized_response) # < ------ OUTLET
|
||||
return number_of_written_bytes
|
||||
number_of_written_bytes = self.sink(serialized_response) # < ------ OUTLET
|
||||
return number_of_written_bytes
|
||||
|
||||
def __emit_rpc_error(self, e):
|
||||
def clear(self):
|
||||
pass
|
||||
|
||||
def message(self,
|
||||
message: str = None,
|
||||
color: str = None,
|
||||
bold: bool = False):
|
||||
if not self.quiet:
|
||||
self.log.info(message)
|
||||
|
||||
def echo(self, *args, **kwds):
|
||||
pass
|
||||
|
||||
def banner(self, banner):
|
||||
pass
|
||||
|
||||
def ipc(self, response: dict, request_id: int, duration) -> int:
|
||||
"""
|
||||
Write RPC response object to stdout and return the number of bytes written.
|
||||
"""
|
||||
|
||||
# Serialize JSON RPC Message
|
||||
assembled_response = self.assemble_response(response=response, message_id=request_id)
|
||||
size = self.__write(data=assembled_response)
|
||||
if not self.quiet:
|
||||
self.log.info(f"OK | Responded to IPC request #{request_id} with {size} bytes, took {duration}")
|
||||
return size
|
||||
|
||||
def error(self, e):
|
||||
"""
|
||||
Write RPC error object to stdout and return the number of bytes written.
|
||||
"""
|
||||
|
@ -185,20 +179,8 @@ class JSONRPCStdoutEmitter(StdoutEmitter):
|
|||
raise self.JSONRPCError
|
||||
|
||||
size = self.__write(data=assembled_error)
|
||||
# if not self.quiet:
|
||||
# self.log.info(f"Error {e.code} | {e.message}") # TODO: Restore this log message
|
||||
return size
|
||||
|
||||
def __emit_rpc_response(self, response: dict, request_id: int, duration) -> int:
|
||||
"""
|
||||
Write RPC response object to stdout and return the number of bytes written.
|
||||
"""
|
||||
|
||||
# Serialize JSON RPC Message
|
||||
assembled_response = self.assemble_response(response=response, message_id=request_id)
|
||||
size = self.__write(data=assembled_response)
|
||||
if not self.quiet:
|
||||
self.log.info(f"OK | Responded to IPC request #{request_id} with {size} bytes, took {duration}")
|
||||
#if not self.quiet:
|
||||
# self.log.info(f"Error {e.code} | {e.message}") # TODO: Restore this log message
|
||||
return size
|
||||
|
||||
|
||||
|
|
|
@ -49,7 +49,7 @@ def character_control_interface(func):
|
|||
duration = responding - received
|
||||
|
||||
# Emit
|
||||
return instance.emitter(response=response, request_id=request_id, duration=duration)
|
||||
return instance.emitter.ipc(response=response, request_id=request_id, duration=duration)
|
||||
|
||||
return wrapped
|
||||
|
||||
|
@ -76,7 +76,7 @@ class AliceInterface(CharacterPublicInterface, AliceSpecification):
|
|||
expiration: maya.MayaDT,
|
||||
value: int = None,
|
||||
) -> dict:
|
||||
|
||||
|
||||
from nucypher.characters.lawful import Bob
|
||||
bob = Bob.from_public_keys(encrypting_key=bob_encrypting_key,
|
||||
verifying_key=bob_verifying_key)
|
||||
|
|
|
@ -22,16 +22,14 @@ from typing import List
|
|||
|
||||
import click
|
||||
import requests
|
||||
from constant_sorrow.constants import NO_BLOCKCHAIN_CONNECTION, NO_PASSWORD
|
||||
from constant_sorrow.constants import NO_BLOCKCHAIN_CONNECTION, NO_PASSWORD, NO_CONTROL_PROTOCOL
|
||||
from nacl.exceptions import CryptoError
|
||||
from twisted.logger import Logger
|
||||
|
||||
from nucypher.blockchain.eth.clients import NuCypherGethGoerliProcess
|
||||
from nucypher.blockchain.eth.token import Stake
|
||||
from nucypher.characters.control.emitters import JSONRPCStdoutEmitter
|
||||
from nucypher.characters.lawful import Ursula
|
||||
from nucypher.cli import painting
|
||||
from nucypher.cli.config import NucypherClickConfig
|
||||
from nucypher.cli.types import IPV4_ADDRESS
|
||||
from nucypher.config.node import CharacterConfiguration
|
||||
from nucypher.network.middleware import RestMiddleware
|
||||
|
@ -62,8 +60,6 @@ SUCCESSFUL_DESTRUCTION = "Successfully destroyed NuCypher configuration"
|
|||
|
||||
LOG = Logger('cli.actions')
|
||||
|
||||
console_emitter = NucypherClickConfig.emit
|
||||
|
||||
|
||||
class UnknownIPAddress(RuntimeError):
|
||||
pass
|
||||
|
@ -77,8 +73,8 @@ def get_password(confirm: bool = False) -> str:
|
|||
return keyring_password
|
||||
|
||||
|
||||
def unlock_nucypher_keyring(password: str, character_configuration: CharacterConfiguration):
|
||||
console_emitter(message='Decrypting NuCypher keyring...', color='yellow')
|
||||
def unlock_nucypher_keyring(emitter, password: str, character_configuration: CharacterConfiguration):
|
||||
emitter.message('Decrypting NuCypher keyring...', color='yellow')
|
||||
if character_configuration.dev_mode:
|
||||
return True # Dev accounts are always unlocked
|
||||
|
||||
|
@ -90,7 +86,8 @@ def unlock_nucypher_keyring(password: str, character_configuration: CharacterCon
|
|||
raise character_configuration.keyring.AuthenticationFailed
|
||||
|
||||
|
||||
def load_seednodes(min_stake: int,
|
||||
def load_seednodes(emitter,
|
||||
min_stake: int,
|
||||
federated_only: bool,
|
||||
network_domains: set,
|
||||
network_middleware: RestMiddleware = None,
|
||||
|
@ -112,7 +109,7 @@ def load_seednodes(min_stake: int,
|
|||
except KeyError:
|
||||
# TODO: If this is a unknown domain, require the caller to pass a teacher URI explicitly?
|
||||
if not teacher_uris:
|
||||
console_emitter(message=f"No default teacher nodes exist for the specified network: {domain}")
|
||||
emitter.message(f"No default teacher nodes exist for the specified network: {domain}")
|
||||
|
||||
for uri in teacher_uris:
|
||||
teacher_node = Ursula.from_teacher_uri(teacher_uri=uri,
|
||||
|
@ -122,7 +119,7 @@ def load_seednodes(min_stake: int,
|
|||
teacher_nodes.append(teacher_node)
|
||||
|
||||
if not teacher_nodes:
|
||||
console_emitter(message=f'WARNING - No Bootnodes Available')
|
||||
emitter.message(f'WARNING - No Bootnodes Available')
|
||||
|
||||
return teacher_nodes
|
||||
|
||||
|
@ -135,7 +132,7 @@ def get_external_ip_from_centralized_source() -> str:
|
|||
f"(status code {ip_request.status_code})")
|
||||
|
||||
|
||||
def determine_external_ip_address(force: bool = False) -> str:
|
||||
def determine_external_ip_address(emitter, force: bool = False) -> str:
|
||||
"""
|
||||
Attempts to automatically get the external IP from ifconfig.me
|
||||
If the request fails, it falls back to the standard process.
|
||||
|
@ -151,12 +148,12 @@ def determine_external_ip_address(force: bool = False) -> str:
|
|||
if not click.confirm(f"Is this the public-facing IPv4 address ({rest_host}) you want to use for Ursula?"):
|
||||
rest_host = click.prompt("Please enter Ursula's public-facing IPv4 address here:", type=IPV4_ADDRESS)
|
||||
else:
|
||||
console_emitter(message=f"WARNING: --force is set, using auto-detected IP '{rest_host}'", color='yellow')
|
||||
emitter.message(f"WARNING: --force is set, using auto-detected IP '{rest_host}'", color='yellow')
|
||||
|
||||
return rest_host
|
||||
|
||||
|
||||
def destroy_configuration(character_config, force: bool = False) -> None:
|
||||
def destroy_configuration(emitter, character_config, force: bool = False) -> None:
|
||||
if not force:
|
||||
click.confirm(CHARACTER_DESTRUCTION.format(name=character_config._NAME,
|
||||
root=character_config.config_root,
|
||||
|
@ -165,17 +162,16 @@ def destroy_configuration(character_config, force: bool = False) -> None:
|
|||
config=character_config.filepath), abort=True)
|
||||
character_config.destroy()
|
||||
SUCCESSFUL_DESTRUCTION = "Successfully destroyed NuCypher configuration"
|
||||
console_emitter(message=SUCCESSFUL_DESTRUCTION, color='green')
|
||||
emitter.message(SUCCESSFUL_DESTRUCTION, color='green')
|
||||
character_config.log.debug(SUCCESSFUL_DESTRUCTION)
|
||||
|
||||
|
||||
def forget(configuration):
|
||||
def forget(emitter, configuration):
|
||||
"""Forget all known nodes via storage"""
|
||||
click.confirm("Permanently delete all known node data?", abort=True)
|
||||
configuration.forget_nodes()
|
||||
message = "Removed all stored node node metadata and certificates"
|
||||
console_emitter(message=message, color='red')
|
||||
click.secho(message=message, fg='red')
|
||||
emitter.message(message, color='red')
|
||||
|
||||
|
||||
def confirm_staged_stake(stakeholder, value, duration):
|
||||
|
@ -183,21 +179,21 @@ def confirm_staged_stake(stakeholder, value, duration):
|
|||
* Ursula Node Operator Notice *
|
||||
-------------------------------
|
||||
|
||||
By agreeing to stake {str(value)} ({str(value.to_nunits())} NuNits):
|
||||
By agreeing to stake {str(value)} ({str(value.to_nunits())} NuNits):
|
||||
|
||||
- Staked tokens will be locked, and unavailable for transactions for the stake duration.
|
||||
|
||||
- You are obligated to maintain a networked and available Ursula-Worker node with the
|
||||
- You are obligated to maintain a networked and available Ursula-Worker node with the
|
||||
for the duration of the stake(s) ({duration} periods)
|
||||
|
||||
- Agree to allow NuCypher network users to carry out uninterrupted re-encryption
|
||||
work orders at-will without interference.
|
||||
work orders at-will without interference.
|
||||
|
||||
Failure to keep your node online, or violation of re-encryption work orders
|
||||
will result in the loss of staked tokens as described in the NuCypher slashing protocol.
|
||||
|
||||
Keeping your Ursula node online during the staking period and successfully
|
||||
performing accurate re-encryption work orders will result in rewards
|
||||
performing accurate re-encryption work orders will result in rewards
|
||||
paid out in ETH retro-actively, on-demand.
|
||||
|
||||
Accept ursula node operator obligation?""", abort=True)
|
||||
|
@ -231,6 +227,8 @@ def make_cli_character(character_config,
|
|||
min_stake: int = 0,
|
||||
**config_args):
|
||||
|
||||
emitter = click_config.emitter
|
||||
|
||||
#
|
||||
# Pre-Init
|
||||
#
|
||||
|
@ -242,13 +240,15 @@ def make_cli_character(character_config,
|
|||
# Handle Keyring
|
||||
if not dev:
|
||||
character_config.attach_keyring()
|
||||
unlock_nucypher_keyring(character_configuration=character_config,
|
||||
unlock_nucypher_keyring(emitter,
|
||||
character_configuration=character_config,
|
||||
password=get_password(confirm=False))
|
||||
|
||||
# Handle Teachers
|
||||
teacher_nodes = None
|
||||
if teacher_uri:
|
||||
teacher_nodes = load_seednodes(teacher_uris=[teacher_uri] if teacher_uri else None,
|
||||
teacher_nodes = load_seednodes(emitter,
|
||||
teacher_uris=[teacher_uri] if teacher_uri else None,
|
||||
min_stake=min_stake,
|
||||
federated_only=character_config.federated_only,
|
||||
network_domains=character_config.domains,
|
||||
|
@ -267,14 +267,12 @@ def make_cli_character(character_config,
|
|||
# Post-Init
|
||||
#
|
||||
|
||||
# TODO: Move to character configuration
|
||||
# Switch to character control emitter
|
||||
if click_config.json_ipc:
|
||||
CHARACTER.controller.emitter = JSONRPCStdoutEmitter(quiet=click_config.quiet)
|
||||
if CHARACTER.controller is not NO_CONTROL_PROTOCOL:
|
||||
CHARACTER.controller.emitter = emitter # TODO: set it on object creation? Or not set at all?
|
||||
|
||||
# Federated
|
||||
if character_config.federated_only:
|
||||
console_emitter(message="WARNING: Running in Federated mode", color='yellow')
|
||||
emitter.message("WARNING: Running in Federated mode", color='yellow')
|
||||
|
||||
return CHARACTER
|
||||
|
||||
|
@ -288,26 +286,26 @@ def select_stake(stakeholder) -> Stake:
|
|||
return chosen_stake
|
||||
|
||||
|
||||
def select_client_account(blockchain, prompt: str = None, default=0) -> str:
|
||||
def select_client_account(emitter, blockchain, prompt: str = None, default=0) -> str:
|
||||
enumerated_accounts = dict(enumerate(blockchain.client.accounts))
|
||||
for index, account in enumerated_accounts.items():
|
||||
click.secho(f"{index} | {account}")
|
||||
emitter.echo(f"{index} | {account}")
|
||||
prompt = prompt or "Select Account"
|
||||
choice = click.prompt(prompt, type=click.IntRange(min=0, max=len(enumerated_accounts)-1), default=default)
|
||||
chosen_account = enumerated_accounts[choice]
|
||||
return chosen_account
|
||||
|
||||
|
||||
def confirm_deployment(deployer) -> bool:
|
||||
def confirm_deployment(emitter, deployer) -> bool:
|
||||
if deployer.blockchain.client.chain_id == 'UNKNOWN' or deployer.blockchain.client.is_local:
|
||||
if click.prompt("Type 'DEPLOY' to continue") != 'DEPLOY':
|
||||
click.secho("Aborting Deployment", fg='red', bold=True)
|
||||
emitter.echo("Aborting Deployment", fg='red', bold=True)
|
||||
raise click.Abort()
|
||||
else:
|
||||
confirmed_chain_id = int(click.prompt("Enter the Chain ID to confirm deployment", type=click.INT))
|
||||
expected_chain_id = int(deployer.blockchain.client.chain_id)
|
||||
if confirmed_chain_id != expected_chain_id:
|
||||
click.secho(f"Chain ID not a match ({confirmed_chain_id} != {expected_chain_id}) Aborting Deployment",
|
||||
emitter.echo(f"Chain ID not a match ({confirmed_chain_id} != {expected_chain_id}) Aborting Deployment",
|
||||
fg='red',
|
||||
bold=True)
|
||||
raise click.Abort()
|
||||
|
|
|
@ -94,9 +94,9 @@ def alice(click_config,
|
|||
raise click.BadOptionUsage(option_name="--geth", message="Federated only cannot be used with the --geth flag")
|
||||
|
||||
# Banner
|
||||
click.clear()
|
||||
if not click_config.json_ipc and not click_config.quiet:
|
||||
click.secho(ALICE_BANNER)
|
||||
emitter = click_config.emitter
|
||||
emitter.clear()
|
||||
emitter.banner(ALICE_BANNER)
|
||||
|
||||
#
|
||||
# Managed Ethereum Client
|
||||
|
@ -135,15 +135,14 @@ def alice(click_config,
|
|||
duration=duration,
|
||||
rate=rate)
|
||||
|
||||
painting.paint_new_installation_help(new_configuration=new_alice_config)
|
||||
painting.paint_new_installation_help(emitter, new_configuration=new_alice_config)
|
||||
return # Exit
|
||||
|
||||
elif action == "view":
|
||||
"""Paint an existing configuration to the console"""
|
||||
configuration_file_location = config_file or AliceConfiguration.default_filepath()
|
||||
response = AliceConfiguration._read_configuration_file(filepath=configuration_file_location)
|
||||
click_config.emit(response)
|
||||
return # Exit
|
||||
return emitter.ipc(response=response, request_id=0, duration=0) # FIXME: what are request_id and duration here?
|
||||
|
||||
#
|
||||
# Make Alice
|
||||
|
@ -171,7 +170,7 @@ def alice(click_config,
|
|||
except FileNotFoundError:
|
||||
return actions.handle_missing_configuration_file(character_config_class=AliceConfiguration,
|
||||
config_file=config_file)
|
||||
|
||||
|
||||
ALICE = actions.make_cli_character(character_config=alice_config,
|
||||
click_config=click_config,
|
||||
dev=dev,
|
||||
|
@ -194,7 +193,7 @@ def alice(click_config,
|
|||
|
||||
# HTTP
|
||||
else:
|
||||
ALICE.controller.emitter(message=f"Alice Verifying Key {bytes(ALICE.stamp).hex()}", color="green", bold=True)
|
||||
emitter.message(f"Alice Verifying Key {bytes(ALICE.stamp).hex()}", color="green", bold=True)
|
||||
controller = ALICE.make_web_controller(crash_on_error=click_config.debug)
|
||||
ALICE.log.info('Starting HTTP Character Web Controller')
|
||||
return controller.start(http_port=controller_port, dry_run=dry_run)
|
||||
|
@ -204,7 +203,7 @@ def alice(click_config,
|
|||
if dev:
|
||||
message = "'nucypher alice destroy' cannot be used in --dev mode"
|
||||
raise click.BadOptionUsage(option_name='--dev', message=message)
|
||||
return actions.destroy_configuration(character_config=alice_config, force=force)
|
||||
return actions.destroy_configuration(emitter, character_config=alice_config, force=force)
|
||||
|
||||
#
|
||||
# Alice API
|
||||
|
|
|
@ -60,9 +60,9 @@ def bob(click_config,
|
|||
#
|
||||
|
||||
# Banner
|
||||
click.clear()
|
||||
if not click_config.json_ipc and not click_config.quiet:
|
||||
click.secho(BOB_BANNER)
|
||||
emitter = click_config.emitter
|
||||
emitter.clear()
|
||||
emitter.banner(BOB_BANNER)
|
||||
|
||||
#
|
||||
# Eager Actions
|
||||
|
@ -86,13 +86,13 @@ def bob(click_config,
|
|||
registry_filepath=registry_filepath,
|
||||
provider_uri=provider_uri)
|
||||
|
||||
return painting.paint_new_installation_help(new_configuration=new_bob_config)
|
||||
return painting.paint_new_installation_help(emitter, new_configuration=new_bob_config)
|
||||
|
||||
# TODO
|
||||
# elif action == "view":
|
||||
# """Paint an existing configuration to the console"""
|
||||
# response = BobConfiguration._read_configuration_file(filepath=config_file or bob_config.config_file_location)
|
||||
# return BOB.controller.emitter(response=response)
|
||||
# return BOB.controller.emitter.ipc(response)
|
||||
|
||||
#
|
||||
# Make Bob
|
||||
|
@ -131,9 +131,6 @@ def bob(click_config,
|
|||
|
||||
if action == "run":
|
||||
|
||||
# Echo Public Keys
|
||||
click_config.emit(message=f"Bob Verifying Key {bytes(BOB.stamp).hex()}", color='green', bold=True)
|
||||
|
||||
# RPC
|
||||
if click_config.json_ipc:
|
||||
rpc_controller = BOB.make_rpc_controller()
|
||||
|
@ -141,9 +138,10 @@ def bob(click_config,
|
|||
rpc_controller.start()
|
||||
return
|
||||
|
||||
click_config.emit(message=f"Bob Verifying Key {bytes(BOB.stamp).hex()}", color='green', bold=True)
|
||||
# Echo Public Keys
|
||||
emitter.message(f"Bob Verifying Key {bytes(BOB.stamp).hex()}", color='green', bold=True)
|
||||
bob_encrypting_key = bytes(BOB.public_keys(DecryptingPower)).hex()
|
||||
click_config.emit(message=f"Bob Encrypting Key {bob_encrypting_key}", color="blue", bold=True)
|
||||
emitter.message(f"Bob Encrypting Key {bob_encrypting_key}", color="blue", bold=True)
|
||||
|
||||
# Start Controller
|
||||
controller = BOB.make_web_controller(crash_on_error=click_config.debug)
|
||||
|
@ -159,7 +157,7 @@ def bob(click_config,
|
|||
raise click.BadOptionUsage(option_name='--dev', message=message)
|
||||
|
||||
# Request
|
||||
return actions.destroy_configuration(character_config=bob_config)
|
||||
return actions.destroy_configuration(emitter, character_config=bob_config)
|
||||
|
||||
#
|
||||
# Bob API Actions
|
||||
|
|
|
@ -28,9 +28,9 @@ def enrico(click_config, action, policy_encrypting_key, dry_run, http_port, mess
|
|||
raise click.BadArgumentUsage('--policy-encrypting-key is required to start Enrico.')
|
||||
|
||||
# Banner
|
||||
click.clear()
|
||||
if not click_config.json_ipc and not click_config.quiet:
|
||||
click.secho(ENRICO_BANNER)
|
||||
emitter = click_config.emitter
|
||||
emitter.clear()
|
||||
emitter.banner(ENRICO_BANNER)
|
||||
|
||||
#
|
||||
# Make Enrico
|
||||
|
@ -38,8 +38,7 @@ def enrico(click_config, action, policy_encrypting_key, dry_run, http_port, mess
|
|||
|
||||
policy_encrypting_key = UmbralPublicKey.from_bytes(bytes.fromhex(policy_encrypting_key))
|
||||
ENRICO = Enrico(policy_encrypting_key=policy_encrypting_key)
|
||||
if click_config.json_ipc:
|
||||
ENRICO.controller.emitter = JSONRPCStdoutEmitter(quiet=click_config.quiet)
|
||||
ENRICO.controller.emitter = emitter # TODO: set it on object creation? Or not set at all?
|
||||
|
||||
#
|
||||
# Actions
|
||||
|
|
|
@ -54,10 +54,11 @@ def felix(click_config,
|
|||
dev,
|
||||
force):
|
||||
|
||||
emitter = click_config.emitter
|
||||
|
||||
# Intro
|
||||
click.clear()
|
||||
if not click_config.quiet:
|
||||
click.secho(FELIX_BANNER.format(checksum_address or ''))
|
||||
emitter.clear()
|
||||
emitter.banner(FELIX_BANNER.format(checksum_address or ''))
|
||||
|
||||
ETH_NODE = NO_BLOCKCHAIN_CONNECTION
|
||||
if geth:
|
||||
|
@ -87,11 +88,11 @@ def felix(click_config,
|
|||
if click_config.debug:
|
||||
raise
|
||||
else:
|
||||
click.secho(str(e), fg='red', bold=True)
|
||||
emitter.echo(str(e), color='red', bold=True)
|
||||
raise click.Abort
|
||||
|
||||
# Paint Help
|
||||
painting.paint_new_installation_help(new_configuration=new_felix_config)
|
||||
painting.paint_new_installation_help(emitter, new_configuration=new_felix_config)
|
||||
|
||||
return # <-- do not remove (conditional flow control)
|
||||
|
||||
|
@ -111,8 +112,8 @@ def felix(click_config,
|
|||
poa=poa)
|
||||
|
||||
except FileNotFoundError:
|
||||
click.secho(f"No Felix configuration file found at {config_file}. "
|
||||
f"Check the filepath or run 'nucypher felix init' to create a new system configuration.")
|
||||
emitter.echo(f"No Felix configuration file found at {config_file}. "
|
||||
f"Check the filepath or run 'nucypher felix init' to create a new system configuration.")
|
||||
raise click.Abort
|
||||
|
||||
try:
|
||||
|
@ -121,10 +122,13 @@ def felix(click_config,
|
|||
felix_config.get_blockchain_interface()
|
||||
|
||||
# Authenticate
|
||||
unlock_nucypher_keyring(character_configuration=felix_config, password=get_password(confirm=False))
|
||||
unlock_nucypher_keyring(emitter,
|
||||
character_configuration=felix_config,
|
||||
password=get_password(confirm=False))
|
||||
|
||||
# Produce Teacher Ursulas
|
||||
teacher_nodes = actions.load_seednodes(teacher_uris=[teacher_uri] if teacher_uri else None,
|
||||
teacher_nodes = actions.load_seednodes(emitter,
|
||||
teacher_uris=[teacher_uri] if teacher_uri else None,
|
||||
min_stake=min_stake,
|
||||
federated_only=felix_config.federated_only,
|
||||
network_domains=felix_config.domains,
|
||||
|
@ -138,7 +142,7 @@ def felix(click_config,
|
|||
if click_config.debug:
|
||||
raise
|
||||
else:
|
||||
click.secho(str(e), fg='red', bold=True)
|
||||
emitter.echo(str(e), color='red', bold=True)
|
||||
raise click.Abort
|
||||
|
||||
if action == "createdb": # Initialize Database
|
||||
|
@ -146,15 +150,15 @@ def felix(click_config,
|
|||
if not force:
|
||||
click.confirm("Overwrite existing database?", abort=True)
|
||||
os.remove(FELIX.db_filepath)
|
||||
click.secho(f"Destroyed existing database {FELIX.db_filepath}")
|
||||
emitter.echo(f"Destroyed existing database {FELIX.db_filepath}")
|
||||
|
||||
FELIX.create_tables()
|
||||
click.secho(f"\nCreated new database at {FELIX.db_filepath}", fg='green')
|
||||
emitter.echo(f"\nCreated new database at {FELIX.db_filepath}", color='green')
|
||||
|
||||
elif action == 'view':
|
||||
token_balance = FELIX.token_balance
|
||||
eth_balance = FELIX.eth_balance
|
||||
click.secho(f"""
|
||||
emitter.echo(f"""
|
||||
Address .... {FELIX.checksum_address}
|
||||
NU ......... {str(token_balance)}
|
||||
ETH ........ {str(eth_balance)}
|
||||
|
@ -163,17 +167,17 @@ ETH ........ {str(eth_balance)}
|
|||
elif action == "accounts":
|
||||
accounts = FELIX.blockchain.client.accounts
|
||||
for account in accounts:
|
||||
click.secho(account)
|
||||
emitter.echo(account)
|
||||
|
||||
elif action == "destroy":
|
||||
"""Delete all configuration files from the disk"""
|
||||
actions.destroy_configuration(character_config=felix_config, force=force)
|
||||
actions.destroy_configuration(emitter, character_config=felix_config, force=force)
|
||||
|
||||
elif action == 'run': # Start web services
|
||||
|
||||
try:
|
||||
click.secho("Waiting for blockchain sync...", fg='yellow')
|
||||
click_config.emit(message=f"Running Felix on {host}:{port}")
|
||||
emitter.echo("Waiting for blockchain sync...", color='yellow')
|
||||
emitter.message(f"Running Felix on {host}:{port}")
|
||||
FELIX.start(host=host,
|
||||
port=port,
|
||||
web_services=not dry_run,
|
||||
|
|
|
@ -23,14 +23,16 @@ def moe(click_config, teacher_uri, min_stake, network, ws_port, dry_run, http_po
|
|||
"Moe" NuCypher node monitor CLI.
|
||||
"""
|
||||
|
||||
emitter = click_config.emitter
|
||||
|
||||
# Banner
|
||||
click.clear()
|
||||
if not click_config.json_ipc and not click_config.quiet:
|
||||
click.secho(MOE_BANNER)
|
||||
emitter.clear()
|
||||
emitter.banner(MOE_BANNER)
|
||||
|
||||
# Teacher Ursula
|
||||
teacher_uris = [teacher_uri] if teacher_uri else None
|
||||
teacher_nodes = actions.load_seednodes(teacher_uris=teacher_uris,
|
||||
teacher_nodes = actions.load_seednodes(emitter,
|
||||
teacher_uris=teacher_uris,
|
||||
min_stake=min_stake,
|
||||
federated_only=True, # TODO: hardcoded for now. Is Moe a Character?
|
||||
network_domains={network} if network else None,
|
||||
|
@ -45,6 +47,6 @@ def moe(click_config, teacher_uri, min_stake, network, ws_port, dry_run, http_po
|
|||
# Run
|
||||
MOE.start_learning_loop(now=learn_on_launch)
|
||||
|
||||
click_config.emit(message=f"Running Moe on 127.0.0.1:{http_port}")
|
||||
emitter.message(f"Running Moe on 127.0.0.1:{http_port}")
|
||||
|
||||
MOE.start(http_port=http_port, ws_port=ws_port, dry_run=dry_run)
|
||||
|
|
|
@ -109,6 +109,8 @@ def ursula(click_config,
|
|||
|
||||
"""
|
||||
|
||||
emitter = click_config.emitter
|
||||
|
||||
#
|
||||
# Validate
|
||||
#
|
||||
|
@ -123,8 +125,7 @@ def ursula(click_config,
|
|||
message="Staking address canot be used in federated mode.")
|
||||
|
||||
# Banner
|
||||
if not click_config.json_ipc and not click_config.quiet:
|
||||
click.secho(URSULA_BANNER.format(worker_address or ''))
|
||||
emitter.banner(URSULA_BANNER.format(worker_address or ''))
|
||||
|
||||
#
|
||||
# Pre-Launch Warnings
|
||||
|
@ -132,9 +133,9 @@ def ursula(click_config,
|
|||
|
||||
if not click_config.quiet:
|
||||
if dev:
|
||||
click.secho("WARNING: Running in Development mode", fg='yellow')
|
||||
emitter.echo("WARNING: Running in Development mode", color='yellow')
|
||||
if force:
|
||||
click.secho("WARNING: Force is enabled", fg='yellow')
|
||||
emitter.echo("WARNING: Force is enabled", color='yellow')
|
||||
|
||||
#
|
||||
# Internal Ethereum Client
|
||||
|
@ -166,16 +167,16 @@ def ursula(click_config,
|
|||
blockchain.connect(fetch_registry=False)
|
||||
|
||||
if not staker_address:
|
||||
staker_address = select_client_account(blockchain=blockchain)
|
||||
staker_address = select_client_account(emitter=emitter, blockchain=blockchain)
|
||||
|
||||
if not worker_address:
|
||||
worker_address = select_client_account(blockchain=blockchain)
|
||||
worker_address = select_client_account(emitter=emitter, blockchain=blockchain)
|
||||
|
||||
if not config_root: # Flag
|
||||
config_root = click_config.config_file # Envvar
|
||||
|
||||
if not rest_host:
|
||||
rest_host = actions.determine_external_ip_address(force=force)
|
||||
rest_host = actions.determine_external_ip_address(emitter, force=force)
|
||||
|
||||
ursula_config = UrsulaConfiguration.generate(password=get_password(confirm=True),
|
||||
config_root=config_root,
|
||||
|
@ -192,7 +193,7 @@ def ursula(click_config,
|
|||
provider_uri=provider_uri,
|
||||
poa=poa)
|
||||
|
||||
painting.paint_new_installation_help(new_configuration=ursula_config)
|
||||
painting.paint_new_installation_help(emitter, new_configuration=ursula_config)
|
||||
return
|
||||
|
||||
#
|
||||
|
@ -232,7 +233,7 @@ def ursula(click_config,
|
|||
if click_config.debug:
|
||||
raise
|
||||
else:
|
||||
click.secho(str(e), fg='red', bold=True)
|
||||
emitter.echo(str(e), color='red', bold=True)
|
||||
raise click.Abort
|
||||
|
||||
#
|
||||
|
@ -245,7 +246,7 @@ def ursula(click_config,
|
|||
if dev:
|
||||
message = "'nucypher ursula destroy' cannot be used in --dev mode - There is nothing to destroy."
|
||||
raise click.BadOptionUsage(option_name='--dev', message=message)
|
||||
return actions.destroy_configuration(character_config=ursula_config, force=force)
|
||||
return actions.destroy_configuration(emitter, character_config=ursula_config, force=force)
|
||||
|
||||
#
|
||||
# Make Ursula
|
||||
|
@ -269,23 +270,23 @@ def ursula(click_config,
|
|||
try:
|
||||
|
||||
# Ursula Deploy Warnings
|
||||
click_config.emit(
|
||||
message="Starting Ursula on {}".format(URSULA.rest_interface),
|
||||
emitter.message(
|
||||
f"Starting Ursula on {URSULA.rest_interface}",
|
||||
color='green',
|
||||
bold=True)
|
||||
|
||||
click_config.emit(
|
||||
message="Connecting to {}".format(','.join(ursula_config.domains)),
|
||||
emitter.message(
|
||||
f"Connecting to {','.join(ursula_config.domains)}",
|
||||
color='green',
|
||||
bold=True)
|
||||
|
||||
click_config.emit(
|
||||
message=f"Working ~ Keep Ursula Online!",
|
||||
emitter.message(
|
||||
"Working ~ Keep Ursula Online!",
|
||||
color='blue',
|
||||
bold=True)
|
||||
|
||||
if interactive:
|
||||
stdio.StandardIO(UrsulaCommandProtocol(ursula=URSULA))
|
||||
stdio.StandardIO(UrsulaCommandProtocol(ursula=URSULA, emitter=emitter))
|
||||
|
||||
if dry_run:
|
||||
return # <-- ABORT - (Last Chance)
|
||||
|
@ -299,47 +300,48 @@ def ursula(click_config,
|
|||
# Handle Crash
|
||||
except Exception as e:
|
||||
ursula_config.log.critical(str(e))
|
||||
click_config.emit(
|
||||
message="{} {}".format(e.__class__.__name__, str(e)),
|
||||
emitter.message(
|
||||
f"{e.__class__.__name__} {e}",
|
||||
color='red',
|
||||
bold=True)
|
||||
raise # Crash :-(
|
||||
|
||||
# Graceful Exit
|
||||
finally:
|
||||
click_config.emit(message="Stopping Ursula", color='green')
|
||||
emitter.message("Stopping Ursula", color='green')
|
||||
ursula_config.cleanup()
|
||||
click_config.emit(message="Ursula Stopped", color='red')
|
||||
emitter.message("Ursula Stopped", color='red')
|
||||
return
|
||||
|
||||
elif action == "save-metadata":
|
||||
"""Manually save a node self-metadata file"""
|
||||
metadata_path = ursula.write_node_metadata(node=URSULA)
|
||||
return click_config.emit(message="Successfully saved node metadata to {}.".format(metadata_path), color='green')
|
||||
emitter.message(f"Successfully saved node metadata to {metadata_path}.", color='green')
|
||||
return
|
||||
|
||||
elif action == "view":
|
||||
"""Paint an existing configuration to the console"""
|
||||
|
||||
if not URSULA.federated_only:
|
||||
click.secho("BLOCKCHAIN ----------\n")
|
||||
painting.paint_contract_status(click_config=click_config, ursula_config=ursula_config)
|
||||
emitter.echo("BLOCKCHAIN ----------\n")
|
||||
painting.paint_contract_status(emitter, ursula_config=ursula_config)
|
||||
current_block = URSULA.blockchain.w3.eth.blockNumber
|
||||
click.secho(f'Block # {current_block}')
|
||||
click.secho(f'NU Balance: {URSULA.token_balance}')
|
||||
click.secho(f'ETH Balance: {URSULA.eth_balance}')
|
||||
click.secho(f'Current Gas Price {URSULA.blockchain.client.gasPrice}')
|
||||
emitter.echo(f'Block # {current_block}')
|
||||
emitter.echo(f'NU Balance: {URSULA.token_balance}')
|
||||
emitter.echo(f'ETH Balance: {URSULA.eth_balance}')
|
||||
emitter.echo(f'Current Gas Price {URSULA.blockchain.client.gasPrice}')
|
||||
|
||||
click.secho("CONFIGURATION --------")
|
||||
emitter.echo("CONFIGURATION --------")
|
||||
response = UrsulaConfiguration._read_configuration_file(filepath=config_file or ursula_config.config_file_location)
|
||||
return click_config.emit(response=response)
|
||||
return emitter.ipc(response=response, request_id=0, duration=0) # FIXME: what are request_id and duration here?
|
||||
|
||||
elif action == "forget":
|
||||
actions.forget(configuration=ursula_config)
|
||||
actions.forget(emitter, configuration=ursula_config)
|
||||
return
|
||||
|
||||
elif action == 'confirm-activity':
|
||||
if not URSULA.stakes:
|
||||
click.secho("There are no active stakes for {}".format(URSULA.checksum_address))
|
||||
emitter.echo(f"There are no active stakes for {URSULA.checksum_address}")
|
||||
return
|
||||
URSULA.staking_agent.confirm_activity(node_address=URSULA.checksum_address)
|
||||
return
|
||||
|
|
|
@ -33,7 +33,6 @@ from nucypher.utilities.sandbox.middleware import MockRestMiddleware
|
|||
class NucypherClickConfig:
|
||||
|
||||
# Output Sinks
|
||||
capture_stdout = False
|
||||
__emitter = None
|
||||
|
||||
# Environment Variables
|
||||
|
@ -60,9 +59,9 @@ class NucypherClickConfig:
|
|||
|
||||
# Session Emitter for pre and post character control engagement.
|
||||
if json_ipc:
|
||||
emitter = JSONRPCStdoutEmitter(quiet=quiet, capture_stdout=NucypherClickConfig.capture_stdout)
|
||||
emitter = JSONRPCStdoutEmitter(quiet=quiet)
|
||||
else:
|
||||
emitter = StdoutEmitter(quiet=quiet, capture_stdout=NucypherClickConfig.capture_stdout)
|
||||
emitter = StdoutEmitter(quiet=quiet)
|
||||
|
||||
self.attach_emitter(emitter)
|
||||
|
||||
|
@ -117,22 +116,22 @@ class NucypherClickConfig:
|
|||
# Only used for testing outputs;
|
||||
# Redirects outputs to in-memory python containers.
|
||||
if mock_networking:
|
||||
self.emit(message="WARNING: Mock networking is enabled")
|
||||
self.emitter.message("WARNING: Mock networking is enabled")
|
||||
self.middleware = MockRestMiddleware()
|
||||
else:
|
||||
self.middleware = RestMiddleware()
|
||||
|
||||
# Global Warnings
|
||||
if self.verbose:
|
||||
self.emit(message="Verbose mode is enabled", color='blue')
|
||||
self.emitter.message("Verbose mode is enabled", color='blue')
|
||||
|
||||
@classmethod
|
||||
def attach_emitter(cls, emitter) -> None:
|
||||
cls.__emitter = emitter
|
||||
|
||||
@classmethod
|
||||
def emit(cls, *args, **kwargs):
|
||||
cls.__emitter(*args, **kwargs)
|
||||
@property
|
||||
def emitter(cls):
|
||||
return cls.__emitter
|
||||
|
||||
|
||||
# Register the above click configuration classes as a decorators
|
||||
|
|
|
@ -30,6 +30,7 @@ from nucypher.blockchain.eth.registry import EthereumContractRegistry
|
|||
from nucypher.blockchain.eth.sol.compile import SolidityCompiler
|
||||
from nucypher.characters.banners import NU_BANNER
|
||||
from nucypher.cli import actions
|
||||
from nucypher.characters.control.emitters import StdoutEmitter
|
||||
from nucypher.cli.actions import get_password, select_client_account
|
||||
from nucypher.cli.painting import paint_contract_deployment
|
||||
from nucypher.cli.types import EIP55_CHECKSUM_ADDRESS, EXISTING_READABLE_FILE
|
||||
|
@ -79,6 +80,8 @@ def deploy(action,
|
|||
|
||||
ETH_NODE = None
|
||||
|
||||
emitter = StdoutEmitter()
|
||||
|
||||
#
|
||||
# Validate
|
||||
#
|
||||
|
@ -115,7 +118,7 @@ def deploy(action,
|
|||
try:
|
||||
blockchain.connect(fetch_registry=False, sync_now=sync)
|
||||
except BlockchainDeployerInterface.ConnectionFailed as e:
|
||||
click.secho(str(e), fg='red', bold=True)
|
||||
emitter.echo(str(e), color='red', bold=True)
|
||||
raise click.Abort()
|
||||
|
||||
#
|
||||
|
@ -123,7 +126,7 @@ def deploy(action,
|
|||
#
|
||||
|
||||
if not deployer_address:
|
||||
deployer_address = select_client_account(blockchain=blockchain)
|
||||
deployer_address = select_client_account(emitter=emitter, blockchain=blockchain)
|
||||
|
||||
# Verify Address
|
||||
if not force:
|
||||
|
@ -138,9 +141,9 @@ def deploy(action,
|
|||
deployer_address=deployer_address)
|
||||
|
||||
# Verify ETH Balance
|
||||
click.secho(f"\n\nDeployer ETH balance: {deployer.eth_balance}")
|
||||
emitter.echo(f"\n\nDeployer ETH balance: {deployer.eth_balance}")
|
||||
if deployer.eth_balance == 0:
|
||||
click.secho("Deployer address has no ETH.", fg='red', bold=True)
|
||||
emitter.echo("Deployer address has no ETH.", color='red', bold=True)
|
||||
raise click.Abort()
|
||||
|
||||
# Add ETH Bootnode or Peer
|
||||
|
@ -179,7 +182,7 @@ def deploy(action,
|
|||
contract_deployer = deployer.deployers[contract_name]
|
||||
except KeyError:
|
||||
message = f"No such contract {contract_name}. Available contracts are {deployer.deployers.keys()}"
|
||||
click.secho(message, fg='red', bold=True)
|
||||
emitter.echo(message, color='red', bold=True)
|
||||
raise click.Abort()
|
||||
else:
|
||||
click.secho(f"Deploying {contract_name}")
|
||||
|
@ -208,34 +211,33 @@ def deploy(action,
|
|||
#
|
||||
secrets = deployer.collect_deployment_secrets()
|
||||
|
||||
click.clear()
|
||||
click.secho(NU_BANNER)
|
||||
emitter.clear()
|
||||
emitter.banner(NU_BANNER)
|
||||
|
||||
click.secho(f"Current Time ........ {maya.now().iso8601()}")
|
||||
click.secho(f"Web3 Provider ....... {deployer.blockchain.provider_uri}")
|
||||
click.secho(f"Block ............... {deployer.blockchain.client.block_number}")
|
||||
click.secho(f"Gas Price ........... {deployer.blockchain.client.gas_price}")
|
||||
emitter.echo(f"Current Time ........ {maya.now().iso8601()}")
|
||||
emitter.echo(f"Web3 Provider ....... {deployer.blockchain.provider_uri}")
|
||||
emitter.echo(f"Block ............... {deployer.blockchain.client.block_number}")
|
||||
emitter.echo(f"Gas Price ........... {deployer.blockchain.client.gas_price}")
|
||||
|
||||
click.secho(f"Deployer Address .... {deployer.checksum_address}")
|
||||
click.secho(f"ETH ................. {deployer.eth_balance}")
|
||||
click.secho(f"Chain ID ............ {deployer.blockchain.client.chain_id}")
|
||||
click.secho(f"Chain Name .......... {deployer.blockchain.client.chain_name}")
|
||||
emitter.echo(f"Deployer Address .... {deployer.checksum_address}")
|
||||
emitter.echo(f"ETH ................. {deployer.eth_balance}")
|
||||
emitter.echo(f"Chain ID ............ {deployer.blockchain.client.chain_id}")
|
||||
emitter.echo(f"Chain Name .......... {deployer.blockchain.client.chain_name}")
|
||||
|
||||
# Ask - Last chance to gracefully abort. This step cannot be forced.
|
||||
click.secho("\nDeployment successfully staged. Take a deep breath. \n", fg='green')
|
||||
|
||||
emitter.echo("\nDeployment successfully staged. Take a deep breath. \n", color='green')
|
||||
# Trigger Deployment
|
||||
if not actions.confirm_deployment(deployer=deployer):
|
||||
if not actions.confirm_deployment(emitter=emitter, deployer=deployer):
|
||||
raise click.Abort()
|
||||
|
||||
# Delay - Last chance to crash and abort
|
||||
click.secho(f"Starting deployment in 3 seconds...", fg='red')
|
||||
emitter.echo(f"Starting deployment in 3 seconds...", color='red')
|
||||
time.sleep(1)
|
||||
click.secho(f"2...", fg='yellow')
|
||||
emitter.echo(f"2...", color='yellow')
|
||||
time.sleep(1)
|
||||
click.secho(f"1...", fg='green')
|
||||
emitter.echo(f"1...", color='green')
|
||||
time.sleep(1)
|
||||
click.secho(f"Deploying...", bold=True)
|
||||
emitter.echo(f"Deploying...", bold=True)
|
||||
|
||||
#
|
||||
# DEPLOY
|
||||
|
@ -248,13 +250,14 @@ def deploy(action,
|
|||
|
||||
# Paint outfile paths
|
||||
# TODO: Echo total gas used.
|
||||
# click.secho("Cumulative Gas Consumption: {} gas".format(total_gas_used), bold=True, fg='blue')
|
||||
# emitter.echo(f"Cumulative Gas Consumption: {total_gas_used} gas", bold=True, color='blue')
|
||||
|
||||
registry_outfile = deployer.blockchain.registry.filepath
|
||||
click.secho('Generated registry {}'.format(registry_outfile), bold=True, fg='blue')
|
||||
emitter.echo('Generated registry {}'.format(registry_outfile), bold=True, color='blue')
|
||||
|
||||
# Save transaction metadata
|
||||
receipts_filepath = deployer.save_deployment_receipts(receipts=deployment_receipts)
|
||||
click.secho(f"Saved deployment receipts to {receipts_filepath}", fg='blue', bold=True)
|
||||
emitter.echo(f"Saved deployment receipts to {receipts_filepath}", color='blue', bold=True)
|
||||
|
||||
elif action == "allocations":
|
||||
if not allocation_infile:
|
||||
|
@ -267,7 +270,7 @@ def deploy(action,
|
|||
token_agent = NucypherTokenAgent(blockchain=blockchain)
|
||||
click.confirm(f"Transfer {amount} from {token_agent.contract_address} to {recipient_address}?", abort=True)
|
||||
txhash = token_agent.transfer(amount=amount, sender_address=token_agent.contract_address, target_address=recipient_address)
|
||||
click.secho(f"OK | {txhash}")
|
||||
emitter.echo(f"OK | {txhash}")
|
||||
|
||||
else:
|
||||
raise click.BadArgumentUsage(message=f"Unknown action '{action}'")
|
||||
|
|
|
@ -24,11 +24,8 @@ from constant_sorrow.constants import NO_KNOWN_NODES
|
|||
from nucypher.blockchain.eth.interfaces import BlockchainInterface
|
||||
from nucypher.blockchain.eth.utils import datetime_at_period
|
||||
from nucypher.characters.banners import NUCYPHER_BANNER
|
||||
from nucypher.characters.control.emitters import StdoutEmitter
|
||||
from nucypher.config.constants import SEEDNODES
|
||||
|
||||
emitter = StdoutEmitter()
|
||||
|
||||
|
||||
def echo_version(ctx, param, value):
|
||||
if not value or ctx.resilient_parsing:
|
||||
|
@ -37,25 +34,25 @@ def echo_version(ctx, param, value):
|
|||
ctx.exit()
|
||||
|
||||
|
||||
def paint_new_installation_help(new_configuration):
|
||||
def paint_new_installation_help(emitter, new_configuration):
|
||||
character_config_class = new_configuration.__class__
|
||||
character_name = character_config_class._NAME.lower()
|
||||
|
||||
emitter(message="Generated keyring {}".format(new_configuration.keyring_root), color='green')
|
||||
emitter(message="Saved configuration file {}".format(new_configuration.config_file_location), color='green')
|
||||
emitter.message("Generated keyring {}".format(new_configuration.keyring_root), color='green')
|
||||
emitter.message("Saved configuration file {}".format(new_configuration.config_file_location), color='green')
|
||||
|
||||
# Felix
|
||||
if character_name == 'felix':
|
||||
suggested_db_command = 'nucypher felix createdb'
|
||||
how_to_proceed_message = f'\nTo initialize a new faucet database run:'
|
||||
emitter(message=how_to_proceed_message, color='green')
|
||||
emitter(message=f'\n\'{suggested_db_command}\'', color='green')
|
||||
emitter.message(how_to_proceed_message, color='green')
|
||||
emitter.message(f'\n\'{suggested_db_command}\'', color='green')
|
||||
|
||||
# Ursula
|
||||
elif character_name == 'ursula' and not new_configuration.federated_only:
|
||||
suggested_staking_command = f'nucypher ursula stake'
|
||||
how_to_stake_message = f"\nTo initialize a NU stake, run '{suggested_staking_command}' or"
|
||||
emitter(message=how_to_stake_message, color='green')
|
||||
emitter.message(how_to_stake_message, color='green')
|
||||
|
||||
# Everyone: Give the use a suggestion as to what to do next
|
||||
vowels = ('a', 'e', 'i', 'o', 'u')
|
||||
|
@ -64,7 +61,7 @@ def paint_new_installation_help(new_configuration):
|
|||
suggested_command = f'nucypher {character_name} run'
|
||||
how_to_run_message = f"\nTo run {adjective} {character_name.capitalize()} node from the default configuration filepath run: \n\n'{suggested_command}'\n"
|
||||
|
||||
return emitter(message=how_to_run_message.format(suggested_command), color='green')
|
||||
emitter.message(how_to_run_message.format(suggested_command), color='green')
|
||||
|
||||
|
||||
def build_fleet_state_status(ursula) -> str:
|
||||
|
@ -84,7 +81,7 @@ def build_fleet_state_status(ursula) -> str:
|
|||
return fleet_state
|
||||
|
||||
|
||||
def paint_node_status(ursula, start_time):
|
||||
def paint_node_status(emitter, ursula, start_time):
|
||||
|
||||
# Build Learning status line
|
||||
learning_status = "Unknown"
|
||||
|
@ -119,10 +116,10 @@ def paint_node_status(ursula, start_time):
|
|||
current_period = f'Current Period ...... {ursula.staking_agent.get_current_period()}'
|
||||
stats.extend([current_period, staking_address])
|
||||
|
||||
click.echo('\n' + '\n'.join(stats) + '\n')
|
||||
emitter.echo('\n' + '\n'.join(stats) + '\n')
|
||||
|
||||
|
||||
def paint_known_nodes(ursula) -> None:
|
||||
def paint_known_nodes(emitter, ursula) -> None:
|
||||
# Gather Data
|
||||
known_nodes = ursula.known_nodes
|
||||
number_of_known_nodes = len(ursula.node_storage.all(federated_only=ursula.federated_only))
|
||||
|
@ -131,17 +128,17 @@ def paint_known_nodes(ursula) -> None:
|
|||
# Operating Mode
|
||||
federated_only = ursula.federated_only
|
||||
if federated_only:
|
||||
click.secho("Configured in Federated Only mode", fg='green')
|
||||
emitter.echo("Configured in Federated Only mode", color='green')
|
||||
|
||||
# Heading
|
||||
label = "Known Nodes (connected {} / seen {})".format(number_of_known_nodes, seen_nodes)
|
||||
heading = '\n' + label + " " * (45 - len(label))
|
||||
click.secho(heading, bold=True, nl=True)
|
||||
emitter.echo(heading, bold=True)
|
||||
|
||||
# Build FleetState status line
|
||||
fleet_state = build_fleet_state_status(ursula=ursula)
|
||||
fleet_status_line = 'Fleet State {}'.format(fleet_state)
|
||||
click.secho(fleet_status_line, fg='blue', bold=True, nl=True)
|
||||
emitter.echo(fleet_status_line, color='blue', bold=True)
|
||||
|
||||
# Legend
|
||||
color_index = {
|
||||
|
@ -152,8 +149,8 @@ def paint_known_nodes(ursula) -> None:
|
|||
|
||||
# Ledgend
|
||||
# for node_type, color in color_index.items():
|
||||
# click.secho('{0:<6} | '.format(node_type), fg=color, nl=False)
|
||||
# click.echo('\n')
|
||||
# emitter.echo('{0:<6} | '.format(node_type), color=color, nl=False)
|
||||
# emitter.echo('\n')
|
||||
|
||||
seednode_addresses = list(bn.checksum_address for bn in SEEDNODES)
|
||||
|
||||
|
@ -166,10 +163,10 @@ def paint_known_nodes(ursula) -> None:
|
|||
elif node.checksum_address in seednode_addresses:
|
||||
node_type = 'seednode'
|
||||
row_template += ' ({})'.format(node_type)
|
||||
click.secho(row_template.format(node.rest_url().ljust(20), node), fg=color_index[node_type])
|
||||
emitter.echo(row_template.format(node.rest_url().ljust(20), node), color=color_index[node_type])
|
||||
|
||||
|
||||
def paint_contract_status(ursula_config, click_config):
|
||||
def paint_contract_status(emitter, ursula_config):
|
||||
contract_payload = """
|
||||
|
||||
| NuCypher ETH Contracts |
|
||||
|
@ -187,7 +184,7 @@ PolicyManager ............ {manager}
|
|||
escrow=ursula_config.staking_agent.contract_address,
|
||||
manager=ursula_config.policy_agent.contract_address,
|
||||
period=ursula_config.staking_agent.get_current_period())
|
||||
click.secho(contract_payload)
|
||||
emitter.echo(contract_payload)
|
||||
|
||||
network_payload = """
|
||||
| Blockchain Network |
|
||||
|
@ -199,10 +196,11 @@ Active Staking Ursulas ... {ursulas}
|
|||
""".format(period=ursula_config.staking_agent.get_current_period(),
|
||||
gas_price=ursula_config.blockchain.client.gasPrice,
|
||||
ursulas=ursula_config.staking_agent.get_staker_population())
|
||||
click.secho(network_payload)
|
||||
emitter.echo(network_payload)
|
||||
|
||||
|
||||
def paint_staged_stake(ursula,
|
||||
def paint_staged_stake(emitter,
|
||||
ursula,
|
||||
stake_value,
|
||||
duration,
|
||||
start_period,
|
||||
|
@ -210,12 +208,12 @@ def paint_staged_stake(ursula,
|
|||
division_message: str = None):
|
||||
|
||||
if division_message:
|
||||
click.secho(f"\n{'=' * 30} ORIGINAL STAKE {'=' * 28}", bold=True)
|
||||
click.secho(division_message)
|
||||
emitter.echo(f"\n{'=' * 30} ORIGINAL STAKE {'=' * 28}", bold=True)
|
||||
emitter.echo(division_message)
|
||||
|
||||
click.secho(f"\n{'=' * 30} STAGED STAKE {'=' * 30}", bold=True)
|
||||
emitter.echo(f"\n{'=' * 30} STAGED STAKE {'=' * 30}", bold=True)
|
||||
|
||||
click.echo(f"""
|
||||
emitter.echo(f"""
|
||||
{ursula}
|
||||
~ Chain -> ID # {ursula.blockchain.client.chain_id} | {ursula.blockchain.client.chain_name}
|
||||
~ Value -> {stake_value} ({Decimal(int(stake_value)):.2E} NuNits)
|
||||
|
@ -224,20 +222,20 @@ def paint_staged_stake(ursula,
|
|||
~ Expiration -> {datetime_at_period(period=end_period)} (period #{end_period})
|
||||
""")
|
||||
|
||||
click.secho('=========================================================================', bold=True)
|
||||
emitter.echo('=========================================================================', bold=True)
|
||||
|
||||
|
||||
def paint_staking_confirmation(ursula, transactions):
|
||||
click.secho(f'\nEscrow Address ... {ursula.staking_agent.contract_address}', fg='blue')
|
||||
def paint_staking_confirmation(emitter, ursula, transactions):
|
||||
emitter.echo(f'\nEscrow Address ... {ursula.staking_agent.contract_address}', color='blue')
|
||||
for tx_name, receipt in transactions.items():
|
||||
click.secho(f'{tx_name.capitalize()} .......... {receipt["transactionHash"].hex()}', fg='green')
|
||||
click.secho(f'''
|
||||
emitter.echo(f'{tx_name.capitalize()} .......... {receipt["transactionHash"].hex()}', color='green')
|
||||
emitter.echo(f'''
|
||||
|
||||
Successfully transmitted stake initialization transactions.
|
||||
|
||||
View your stakes by running 'nucypher stake list'
|
||||
or set your Ursula worker node address by running 'nucypher stake set-worker'.
|
||||
''', fg='green')
|
||||
''', color='green')
|
||||
|
||||
|
||||
def prettify_stake(stake, index: int = None) -> str:
|
||||
|
@ -259,25 +257,25 @@ def prettify_stake(stake, index: int = None) -> str:
|
|||
return pretty
|
||||
|
||||
|
||||
def paint_stakes(stakes):
|
||||
def paint_stakes(emitter, stakes):
|
||||
|
||||
title = "=========================== Active Stakes ==============================\n"
|
||||
|
||||
header = f'| ~ | Staker | Worker | # | Value | Duration | Enactment '
|
||||
breaky = f'| | ------ | ------ | - | -------- | ------------ | ------------------ '
|
||||
|
||||
click.secho(title)
|
||||
click.secho(header, bold=True)
|
||||
click.secho(breaky, bold=True)
|
||||
emitter.echo(title)
|
||||
emitter.echo(header, bold=True)
|
||||
emitter.echo(breaky, bold=True)
|
||||
for index, stake in enumerate(stakes):
|
||||
row = prettify_stake(stake=stake, index=index)
|
||||
row_color = 'yellow' if stake.worker_address == BlockchainInterface.NULL_ADDRESS else 'white'
|
||||
click.secho(row, fg=row_color)
|
||||
click.secho('') # newline
|
||||
return
|
||||
emitter.echo(row, color=row_color)
|
||||
emitter.echo('') # newline
|
||||
|
||||
|
||||
def paint_staged_stake_division(ursula,
|
||||
def paint_staged_stake_division(emitter,
|
||||
ursula,
|
||||
original_stake,
|
||||
target_value,
|
||||
extension):
|
||||
|
@ -290,7 +288,8 @@ def paint_staged_stake_division(ursula,
|
|||
~ Original Stake: {prettify_stake(stake=original_stake, index=None)}
|
||||
"""
|
||||
|
||||
paint_staged_stake(ursula=ursula,
|
||||
paint_staged_stake(emitter=emitter,
|
||||
ursula=ursula,
|
||||
stake_value=target_value,
|
||||
duration=new_duration,
|
||||
start_period=original_stake.start_period,
|
||||
|
@ -300,6 +299,8 @@ def paint_staged_stake_division(ursula,
|
|||
|
||||
def paint_contract_deployment(contract_name: str, contract_address: str, receipts: dict):
|
||||
|
||||
# TODO: switch to using an explicit emitter
|
||||
|
||||
# Paint heading
|
||||
heading = '\n{} ({})'.format(contract_name, contract_address)
|
||||
click.secho(heading, bold=True)
|
||||
|
|
|
@ -35,10 +35,11 @@ class UrsulaCommandProtocol(LineReceiver):
|
|||
encoding = 'utf-8'
|
||||
delimiter = os.linesep.encode(encoding=encoding)
|
||||
|
||||
def __init__(self, ursula):
|
||||
def __init__(self, ursula, emitter):
|
||||
super().__init__()
|
||||
|
||||
self.ursula = ursula
|
||||
self.emitter = emitter
|
||||
self.start_time = maya.now()
|
||||
|
||||
self.__history = deque(maxlen=10)
|
||||
|
@ -78,11 +79,11 @@ class UrsulaCommandProtocol(LineReceiver):
|
|||
"""
|
||||
Display this help message.
|
||||
"""
|
||||
click.secho("\nUrsula Command Help\n===================\n")
|
||||
self.emitter.echo("\nUrsula Command Help\n===================\n")
|
||||
for command, func in self.__commands.items():
|
||||
if '?' not in command:
|
||||
try:
|
||||
click.secho(f'{command}\n{"-"*len(command)}\n{func.__doc__.lstrip()}')
|
||||
self.emitter.echo(f'{command}\n{"-"*len(command)}\n{func.__doc__.lstrip()}')
|
||||
except AttributeError:
|
||||
raise AttributeError("Ursula Command method is missing a docstring,"
|
||||
" which is required for generating help text.")
|
||||
|
@ -92,7 +93,7 @@ class UrsulaCommandProtocol(LineReceiver):
|
|||
Display a list of all known nucypher peers.
|
||||
"""
|
||||
from nucypher.cli.painting import paint_known_nodes
|
||||
paint_known_nodes(ursula=self.ursula)
|
||||
paint_known_nodes(emitter=self.emitter, ursula=self.ursula)
|
||||
|
||||
def paintStakes(self):
|
||||
"""
|
||||
|
@ -100,23 +101,23 @@ class UrsulaCommandProtocol(LineReceiver):
|
|||
"""
|
||||
from nucypher.cli.painting import paint_stakes
|
||||
if self.ursula.stakes:
|
||||
paint_stakes(stakes=self.ursula.stakes)
|
||||
paint_stakes(self.emitter, stakes=self.ursula.stakes)
|
||||
else:
|
||||
click.secho("No active stakes.")
|
||||
self.emitter.echo("No active stakes.")
|
||||
|
||||
def paintStatus(self):
|
||||
"""
|
||||
Display the current status of the attached Ursula node.
|
||||
"""
|
||||
from nucypher.cli.painting import paint_node_status
|
||||
paint_node_status(ursula=self.ursula, start_time=self.start_time)
|
||||
paint_node_status(emitter=self.emitter, ursula=self.ursula, start_time=self.start_time)
|
||||
|
||||
def paintFleetState(self):
|
||||
"""
|
||||
Display information about the network-wide fleet state as the attached Ursula node sees it.
|
||||
"""
|
||||
line = '{}'.format(build_fleet_state_status(ursula=self.ursula))
|
||||
click.secho(line)
|
||||
self.emitter.echo(line)
|
||||
|
||||
def connectionMade(self):
|
||||
|
||||
|
@ -124,10 +125,10 @@ class UrsulaCommandProtocol(LineReceiver):
|
|||
self.ursula.checksum_address,
|
||||
self.ursula.rest_url())
|
||||
|
||||
click.secho(message, fg='green')
|
||||
click.secho('{} | {}'.format(self.ursula.nickname_icon, self.ursula.nickname), fg='blue', bold=True)
|
||||
self.emitter.echo(message, color='green')
|
||||
self.emitter.echo('{} | {}'.format(self.ursula.nickname_icon, self.ursula.nickname), color='blue', bold=True)
|
||||
|
||||
click.secho("\nType 'help' or '?' for help")
|
||||
self.emitter.echo("\nType 'help' or '?' for help")
|
||||
self.transport.write(self.prompt)
|
||||
|
||||
def connectionLost(self, reason=connectionDone) -> None:
|
||||
|
@ -149,7 +150,7 @@ class UrsulaCommandProtocol(LineReceiver):
|
|||
# Print
|
||||
except KeyError:
|
||||
if line: # allow for empty string
|
||||
click.secho("Invalid input. Options are {}".format(', '.join(self.__commands.keys())))
|
||||
self.emitter.echo("Invalid input. Options are {}".format(', '.join(self.__commands.keys())))
|
||||
|
||||
else:
|
||||
self.__history.append(raw_line)
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
|
||||
import click
|
||||
from constant_sorrow.constants import NO_STAKING_DEVICE
|
||||
from web3 import Web3
|
||||
|
||||
from nucypher.blockchain.eth.actors import StakeHolder
|
||||
|
@ -69,9 +68,9 @@ def stake(click_config,
|
|||
) -> None:
|
||||
|
||||
# Banner
|
||||
if not click_config.quiet:
|
||||
click.clear()
|
||||
click.secho(NU_BANNER)
|
||||
emitter = click_config.emitter
|
||||
emitter.clear()
|
||||
emitter.banner(NU_BANNER)
|
||||
|
||||
if action == 'new-stakeholder':
|
||||
|
||||
|
@ -93,7 +92,7 @@ def stake(click_config,
|
|||
blockchain=blockchain)
|
||||
|
||||
filepath = new_stakeholder.to_configuration_file(override=force)
|
||||
click.secho(f"Wrote new stakeholder configuration to {filepath}", fg='green')
|
||||
emitter.echo(f"Wrote new stakeholder configuration to {filepath}", color='green')
|
||||
return # Exit
|
||||
|
||||
#
|
||||
|
@ -110,21 +109,21 @@ def stake(click_config,
|
|||
|
||||
if action == 'list':
|
||||
if not STAKEHOLDER.stakes:
|
||||
click.echo(f"There are no active stakes")
|
||||
emitter.echo(f"There are no active stakes")
|
||||
else:
|
||||
painting.paint_stakes(stakes=STAKEHOLDER.stakes)
|
||||
painting.paint_stakes(emitter=emitter, stakes=STAKEHOLDER.stakes)
|
||||
return
|
||||
|
||||
elif action == 'accounts':
|
||||
for address, balances in STAKEHOLDER.account_balances.items():
|
||||
click.secho(f"{address} | {Web3.fromWei(balances['ETH'], 'ether')} ETH | {NU.from_nunits(balances['NU'])}")
|
||||
emitter.echo(f"{address} | {Web3.fromWei(balances['ETH'], 'ether')} ETH | {NU.from_nunits(balances['NU'])}")
|
||||
return # Exit
|
||||
|
||||
elif action == 'sync':
|
||||
click.secho("Reading on-chain stake data...")
|
||||
emitter.echo("Reading on-chain stake data...")
|
||||
STAKEHOLDER.read_onchain_stakes()
|
||||
STAKEHOLDER.to_configuration_file(override=True)
|
||||
click.secho("OK!", fg='green')
|
||||
emitter.echo("OK!", color='green')
|
||||
return # Exit
|
||||
|
||||
elif action == 'set-worker':
|
||||
|
@ -142,7 +141,7 @@ def stake(click_config,
|
|||
password=password,
|
||||
worker_address=worker_address)
|
||||
|
||||
click.secho("OK!", fg='green')
|
||||
emitter.echo("OK!", color='green')
|
||||
return # Exit
|
||||
|
||||
elif action == 'init':
|
||||
|
@ -181,7 +180,8 @@ def stake(click_config,
|
|||
#
|
||||
|
||||
if not force:
|
||||
painting.paint_staged_stake(ursula=STAKEHOLDER,
|
||||
painting.paint_staged_stake(emitter=emitter,
|
||||
ursula=STAKEHOLDER,
|
||||
stake_value=value,
|
||||
duration=duration,
|
||||
start_period=start_period,
|
||||
|
@ -198,7 +198,9 @@ def stake(click_config,
|
|||
checksum_address=staking_address,
|
||||
password=password)
|
||||
|
||||
painting.paint_staking_confirmation(ursula=STAKEHOLDER, transactions=new_stake.transactions)
|
||||
painting.paint_staking_confirmation(emitter=emitter,
|
||||
ursula=STAKEHOLDER,
|
||||
transactions=new_stake.transactions)
|
||||
return # Exit
|
||||
|
||||
elif action == 'divide':
|
||||
|
@ -227,7 +229,8 @@ def stake(click_config,
|
|||
extension = duration
|
||||
|
||||
if not force:
|
||||
painting.paint_staged_stake_division(ursula=STAKEHOLDER,
|
||||
painting.paint_staged_stake_division(emitter=emitter,
|
||||
ursula=STAKEHOLDER,
|
||||
original_stake=current_stake,
|
||||
target_value=value,
|
||||
extension=extension)
|
||||
|
@ -243,11 +246,11 @@ def stake(click_config,
|
|||
duration=extension,
|
||||
password=password)
|
||||
if not click_config.quiet:
|
||||
click.secho('Successfully divided stake', fg='green')
|
||||
click.secho(f'Receipt ........... {new_stake.receipt}')
|
||||
emitter.echo('Successfully divided stake', color='green')
|
||||
emitter.echo(f'Receipt ........... {new_stake.receipt}')
|
||||
|
||||
# Show the resulting stake list
|
||||
painting.paint_stakes(stakes=STAKEHOLDER.stakes)
|
||||
painting.paint_stakes(emitter=emitter, stakes=STAKEHOLDER.stakes)
|
||||
return # Exit
|
||||
|
||||
elif action == 'collect-reward':
|
||||
|
|
|
@ -42,7 +42,7 @@ def status(click_config, config_file):
|
|||
ursula_config.acquire_agency()
|
||||
|
||||
# Contracts
|
||||
paint_contract_status(ursula_config=ursula_config, click_config=click_config)
|
||||
paint_contract_status(click_config.emitter, ursula_config=ursula_config, click_config=click_config)
|
||||
|
||||
# Known Nodes
|
||||
paint_known_nodes(ursula=ursula_config)
|
||||
paint_known_nodes(emitter=click_config.emitter, ursula=ursula_config)
|
||||
|
|
|
@ -4,6 +4,7 @@ from contextlib import contextmanager
|
|||
import pytest
|
||||
from io import StringIO
|
||||
|
||||
from nucypher.characters.control.emitters import StdoutEmitter
|
||||
from nucypher.cli.config import NucypherClickConfig
|
||||
from nucypher.cli.processes import UrsulaCommandProtocol
|
||||
|
||||
|
@ -31,13 +32,15 @@ def ursula(federated_ursulas):
|
|||
|
||||
@pytest.fixture(scope='module')
|
||||
def protocol(ursula):
|
||||
protocol = UrsulaCommandProtocol(ursula=ursula)
|
||||
emitter = StdoutEmitter()
|
||||
protocol = UrsulaCommandProtocol(ursula=ursula, emitter=emitter)
|
||||
return protocol
|
||||
|
||||
|
||||
def test_ursula_command_protocol_creation(ursula):
|
||||
|
||||
protocol = UrsulaCommandProtocol(ursula=ursula)
|
||||
emitter = StdoutEmitter()
|
||||
protocol = UrsulaCommandProtocol(ursula=ursula, emitter=emitter)
|
||||
|
||||
assert protocol.ursula == ursula
|
||||
assert b'Ursula' in protocol.prompt
|
||||
|
|
Loading…
Reference in New Issue