diff --git a/cli/main.py b/cli/main.py
index 08e94f0b2..0fba9c5a1 100644
--- a/cli/main.py
+++ b/cli/main.py
@@ -1,4 +1,5 @@
#!/usr/bin/env python3
+
"""
This file is part of nucypher.
@@ -14,47 +15,43 @@ GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with nucypher. If not, see .
+
"""
import collections
import hashlib
import json
-import logging
import os
import shutil
-import sys
from typing import Tuple, ClassVar
import click
-from constant_sorrow import constants
+from constant_sorrow.constants import NO_NODE_CONFIGURATION, NO_BLOCKCHAIN_CONNECTION
from eth_utils import is_checksum_address
+from sentry_sdk.integrations.logging import LoggingIntegration
from twisted.internet import stdio
+from twisted.logger import Logger
+from twisted.logger import globalLogPublisher
from web3.middleware import geth_poa_middleware
import nucypher
from nucypher.blockchain.eth.agents import MinerAgent, PolicyAgent, NucypherTokenAgent, EthereumContractAgent
from nucypher.blockchain.eth.chains import Blockchain
-
from nucypher.blockchain.eth.constants import (MIN_ALLOWED_LOCKED,
MIN_LOCKED_PERIODS,
MAX_MINTING_PERIODS)
-
from nucypher.blockchain.eth.deployers import (NucypherTokenDeployer,
MinerEscrowDeployer,
PolicyManagerDeployer)
-
from nucypher.characters.lawful import Ursula
from nucypher.config.characters import UrsulaConfiguration
-
from nucypher.config.constants import (SEEDNODES,
SeednodeMetadata,
NUCYPHER_SENTRY_ENDPOINT,
- REPORT_TO_SENTRY,
- DEBUG,
KEYRING_PASSPHRASE_ENVVAR_KEY)
-
from nucypher.config.keyring import NucypherKeyring
from nucypher.config.node import NodeConfiguration
+from nucypher.utilities.logging import logToSentry
from nucypher.utilities.sandbox.ursula import UrsulaCommandProtocol
BANNER = """
@@ -71,68 +68,53 @@ BANNER = """
""".format(nucypher.__version__)
-
-def echo_version(ctx, param, value):
- if not value or ctx.resilient_parsing:
- return
- click.secho(BANNER, bold=True)
- ctx.exit()
-
-
-# Setup Logging #
-################
-
-root = logging.Logger("cli")
-if DEBUG:
- root.setLevel(logging.DEBUG)
- ch = logging.StreamHandler(sys.stdout)
- ch.setLevel(logging.DEBUG)
- formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
- ch.setFormatter(formatter)
- root.addHandler(ch)
-
-# Report to Sentry #
-####################
-
-if not hasattr(sys, '_pytest_is_running') and REPORT_TO_SENTRY:
- import sentry_sdk
- sentry_sdk.init(NUCYPHER_SENTRY_ENDPOINT, release=nucypher.__version__)
-
-####################
-
# Pending Configuration Named Tuple
-fields = 'passphrase wallet signing tls skip_keys save_file'.split()
-PendingConfigurationDetails = collections.namedtuple('PendingConfigurationDetails', fields)
+PendingConfigurationDetails = collections.namedtuple('PendingConfigurationDetails',
+ 'passphrase wallet signing tls skip_keys save_file')
class NucypherClickConfig:
+ log_to_sentry = True
+
def __init__(self):
- self.log = logging.Logger(self.__class__.__name__)
+ if self.log_to_sentry:
+ import sentry_sdk
+ import logging
+ sentry_logging = LoggingIntegration(
+ level=logging.INFO, # Capture info and above as breadcrumbs
+ event_level=logging.DEBUG # Send debug logs as events
+ )
+ sentry_sdk.init(
+ dsn=NUCYPHER_SENTRY_ENDPOINT,
+ integrations=[sentry_logging],
+ release=nucypher.__version__
+ )
+
+ globalLogPublisher.addObserver(logToSentry)
+
+ self.log = Logger(self.__class__.__name__)
# Node Configuration
- self.node_configuration = constants.NO_NODE_CONFIGURATION
- self.dev = constants.NO_NODE_CONFIGURATION
- self.federated_only = constants.NO_NODE_CONFIGURATION
- self.config_root = constants.NO_NODE_CONFIGURATION
- self.config_file = constants.NO_NODE_CONFIGURATION
+ self.node_configuration = NO_NODE_CONFIGURATION
+ self.dev = NO_NODE_CONFIGURATION
+ self.federated_only = NO_NODE_CONFIGURATION
+ self.config_root = NO_NODE_CONFIGURATION
+ self.config_file = NO_NODE_CONFIGURATION
# Blockchain
- self.deployer = constants.NO_BLOCKCHAIN_CONNECTION
- self.compile = constants.NO_BLOCKCHAIN_CONNECTION
- self.poa = constants.NO_BLOCKCHAIN_CONNECTION
- self.blockchain = constants.NO_BLOCKCHAIN_CONNECTION
- self.provider_uri = constants.NO_BLOCKCHAIN_CONNECTION
- self.registry_filepath = constants.NO_BLOCKCHAIN_CONNECTION
- self.accounts = constants.NO_BLOCKCHAIN_CONNECTION
+ self.deployer = NO_BLOCKCHAIN_CONNECTION
+ self.compile = NO_BLOCKCHAIN_CONNECTION
+ self.poa = NO_BLOCKCHAIN_CONNECTION
+ self.blockchain = NO_BLOCKCHAIN_CONNECTION
+ self.provider_uri = NO_BLOCKCHAIN_CONNECTION
+ self.registry_filepath = NO_BLOCKCHAIN_CONNECTION
+ self.accounts = NO_BLOCKCHAIN_CONNECTION
# Agency
- self.token_agent = constants.NO_BLOCKCHAIN_CONNECTION
- self.miner_agent = constants.NO_BLOCKCHAIN_CONNECTION
- self.policy_agent = constants.NO_BLOCKCHAIN_CONNECTION
-
- # Simulation
- self.sim_processes = constants.NO_SIMULATION_RUNNING
+ self.token_agent = NO_BLOCKCHAIN_CONNECTION
+ self.miner_agent = NO_BLOCKCHAIN_CONNECTION
+ self.policy_agent = NO_BLOCKCHAIN_CONNECTION
def get_node_configuration(self, configuration_class=UrsulaConfiguration, **overrides):
if self.dev:
@@ -204,7 +186,6 @@ class NucypherClickConfig:
# Defaults
passphrase = None
- host = UrsulaConfiguration.DEFAULT_REST_HOST
skip_all_key_generation, generate_wallet = False, False
generate_encrypting_keys, generate_tls_keys, save_node_configuration_file = True, True, True
@@ -218,7 +199,7 @@ class NucypherClickConfig:
if generate_tls_keys or force:
if not force and not rest_host:
- rest_host = click.prompt("Enter node IPv4 Address",
+ rest_host = click.prompt("Enter Node's Public IPv4 Address",
default=UrsulaConfiguration.DEFAULT_REST_HOST,
type=click.STRING)
@@ -299,10 +280,19 @@ Located at {}?'''.format(self.node_configuration.config_root), abort=True)
click.secho("Deleted configuration files at {}".format(self.node_configuration.config_root), fg='blue')
+#########################################
+
# Register the above class as a decorator
uses_config = click.make_pass_decorator(NucypherClickConfig, ensure=True)
+def echo_version(ctx, param, value):
+ if not value or ctx.resilient_parsing:
+ return
+ click.secho(BANNER, bold=True)
+ ctx.exit()
+
+
# Custom input type
class ChecksumAddress(click.ParamType):
name = 'checksum_address'
@@ -315,6 +305,12 @@ class ChecksumAddress(click.ParamType):
CHECKSUM_ADDRESS = ChecksumAddress()
+########################################
+
+
+#
+# CLI Commands
+#
@click.group()
@click.option('--version', help="Echo the CLI version", is_flag=True, callback=echo_version, expose_value=False, is_eager=True)
@@ -348,6 +344,7 @@ def cli(config,
# Store config data
config.verbose = verbose
+ # CLI Node View
config.dev = dev
config.federated_only = federated_only
config.config_root = config_root
@@ -359,14 +356,6 @@ def cli(config,
config.deployer = deployer
config.poa = poa
- # TODO: Create NodeConfiguration from these values
- # node_configuration = NodeConfiguration(temp=dev,
- # federated_only=federated_only,
- # config_root=config_root,
- # known_metadata_dir=metadata_dir,
- # registry_filepath=registry_filepath,
- # )
-
if config.verbose:
click.secho("Verbose mode is enabled", fg='blue')
@@ -378,7 +367,6 @@ def cli(config,
@cli.command()
@click.option('--ursula', help="Configure ursula", is_flag=True, default=False)
-@click.option('--filesystem', is_flag=True, default=False)
@click.option('--rest-host', type=click.STRING)
@click.option('--no-registry', help="Skip importing the default contract registry", is_flag=True)
@click.option('--force', help="Ask confirm once; Do not generate wallet or certificate", is_flag=True)
@@ -388,32 +376,22 @@ def configure(config,
action,
ursula,
rest_host,
- filesystem,
no_registry,
force):
- """Manage local nucypher files and directories"""
- config.get_node_configuration(configuration_class=UrsulaConfiguration, rest_host=rest_host) # TODO: Un-hardcode Ursula
+
+ # Fetch Existing Configuration
+ config.get_node_configuration(configuration_class=UrsulaConfiguration, rest_host=rest_host)
+
if action == "install":
config.create_new_configuration(ursula=ursula, force=force, no_registry=no_registry, rest_host=rest_host)
elif action == "view":
- json_config = NodeConfiguration._read_configuration_file(filepath=config.node_configuration.config_file_location)
+ json_config = UrsulaConfiguration._read_configuration_file(filepath=config.node_configuration.config_file_location)
click.echo(json_config)
elif action == "reset":
config.destroy_configuration()
config.create_new_configuration(ursula=ursula, force=force, no_registry=no_registry, rest_host=rest_host)
elif action == "destroy":
config.destroy_configuration()
- elif action == "validate":
- is_valid = True # Until there is a reason to believe otherwise...
- try:
- if filesystem: # Check runtime directory
- is_valid = NodeConfiguration.validate(config_root=config.node_configuration.config_root,
- no_registry=no_registry)
- except NodeConfiguration.InvalidConfiguration:
- is_valid = False
- finally:
- result = 'Valid' if is_valid else 'Invalid'
- click.echo('{} is {}'.format(config.node_configuration.config_root, result))
else:
raise click.BadArgumentUsage("No such argument {}".format(action))
@@ -439,13 +417,13 @@ def accounts(config,
checksum_address = config.blockchain.interface.w3.eth.coinbase
click.echo("WARNING: No checksum address specified - Using the node's default account.")
- def __collect_transfer_details(denomination: str):
- destination = click.prompt("Enter destination checksum_address")
- if not is_checksum_address(destination):
- click.secho("{} is not a valid checksum checksum_address".format(destination), fg='red', bold=True)
- raise click.Abort()
- amount = click.prompt("Enter amount of {} to transfer".format(denomination), type=click.INT)
- return destination, amount
+ def __collect_transfer_details(denomination: str):
+ destination = click.prompt("Enter destination checksum_address")
+ if not is_checksum_address(destination):
+ click.secho("{} is not a valid checksum checksum_address".format(destination), fg='red', bold=True)
+ raise click.Abort()
+ amount = click.prompt("Enter amount of {} to transfer".format(denomination), type=click.INT)
+ return destination, amount
#
# Action Switch
@@ -565,7 +543,7 @@ def stake(config,
address = config.accounts[account_selection]
if action == 'list':
- live_stakes = config.miner_agent.get_all_stakes(miner_address=address)
+ live_stakes = config.miner_agent.get_all_stakes(miner_address=checksum_address)
for index, stake_info in enumerate(live_stakes):
row = '{} | {}'.format(index, stake_info)
click.echo(row)
@@ -573,12 +551,12 @@ def stake(config,
elif action == 'init':
click.confirm("Stage a new stake?", abort=True)
- live_stakes = config.miner_agent.get_all_stakes(miner_address=address)
+ live_stakes = config.miner_agent.get_all_stakes(miner_address=checksum_address)
if len(live_stakes) > 0:
- raise RuntimeError("There is an existing stake for {}".format(address))
+ raise RuntimeError("There is an existing stake for {}".format(checksum_address))
# Value
- balance = config.token_agent.get_balance(address=address)
+ balance = config.token_agent.get_balance(address=checksum_address)
click.echo("Current balance: {}".format(balance))
value = click.prompt("Enter stake value", type=click.INT)
@@ -586,7 +564,7 @@ def stake(config,
message = "Minimum duration: {} | Maximum Duration: {}".format(MIN_LOCKED_PERIODS,
MAX_MINTING_PERIODS)
click.echo(message)
- duration = click.prompt("Enter stake duration in days", type=click.INT)
+ duration = click.prompt("Enter stake duration in periods (1 Period = 24 Hours)", type=click.INT)
start_period = config.miner_agent.get_current_period()
end_period = start_period + duration
@@ -602,45 +580,28 @@ def stake(config,
Start Period: {start_period}
End Period: {end_period}
- """.format(address=address,
+ """.format(address=checksum_address,
value=value,
duration=duration,
start_period=start_period,
end_period=end_period))
# TODO: Ursula Process management
- # if not click.confirm("Is this correct?"):
- # # field = click.prompt("Which stake field do you want to edit?")
- # raise NotImplementedError
- #
- # # Initialize the staged stake
- # config.__proxy_contract.deposit_tokens(amount=value, lock_periods=duration, sender_address=address)
- #
- # proc_params = ['run_ursula']
- # processProtocol = UrsulaCommandProtocol(command=proc_params, checksum_address=checksum_address)
- # ursula_proc = reactor.spawnProcess(processProtocol, "nucypher", proc_params)
- raise NotImplementedError
-
- elif action == 'resume':
- """Reconnect and resume an existing live stake"""
- # proc_params = ['run_ursula']
- # processProtocol = UrsulaCommandProtocol(command=proc_params, checksum_address=checksum_address)
- # ursula_proc = reactor.spawnProcess(processProtocol, "nucypher", proc_params)
raise NotImplementedError
elif action == 'confirm-activity':
"""Manually confirm activity for the active period"""
- stakes = config.miner_agent.get_all_stakes(miner_address=address)
+ stakes = config.miner_agent.get_all_stakes(miner_address=checksum_address)
if len(stakes) == 0:
- raise RuntimeError("There are no active stakes for {}".format(address))
- config.miner_agent.confirm_activity(node_address=address)
+ raise RuntimeError("There are no active stakes for {}".format(checksum_address))
+ config.miner_agent.confirm_activity(node_address=checksum_address)
elif action == 'divide':
"""Divide an existing stake by specifying the new target value and end period"""
- stakes = config.miner_agent.get_all_stakes(miner_address=address)
+ stakes = config.miner_agent.get_all_stakes(miner_address=checksum_address)
if len(stakes) == 0:
- raise RuntimeError("There are no active stakes for {}".format(address))
+ raise RuntimeError("There are no active stakes for {}".format(checksum_address))
if not index:
for selection_index, stake_info in enumerate(stakes):
@@ -661,7 +622,7 @@ def stake(config,
target_value+extension))
click.confirm("Is this correct?", abort=True)
- config.miner_agent.divide_stake(miner_address=address,
+ config.miner_agent.divide_stake(miner_address=checksum_address,
stake_index=index,
value=value,
periods=extension)
@@ -723,12 +684,7 @@ def deploy(config,
upgradeable=True,
agent_name='policy_agent',
dependant='miner_agent'
- ),
-
- # UserEscrowDeployer._contract_name: DeployerInfo(deployer_class=UserEscrowDeployer,
- # upgradeable=True,
- # agent_name='user_agent',
- # dependant='policy_agent'), # TODO: User Escrow CLI Deployment
+ )
})
click.confirm("Continue?", abort=True)
@@ -829,7 +785,7 @@ def deploy(config,
click.echo("{}:{}".format(tx_name, txhash))
if not force and click.confirm("Save transaction hashes to JSON file?"):
- file = click.prompt("Enter output filepath", type=click.File(mode='w')) # TODO
+ file = click.prompt("Enter output filepath", type=click.File(mode='w')) # TODO: Save Txhashes
file.write(json.dumps(__deployment_transactions))
click.secho("Successfully wrote transaction hashes file to {}".format(file.path), fg='green')
@@ -838,12 +794,8 @@ def deploy(config,
@cli.command()
-@click.option('--contracts', help="Echo nucypher smart contract info", is_flag=True)
-@click.option('--network', help="Echo the network status", is_flag=True)
@uses_config
-def status(config,
- contracts,
- network):
+def status(config):
"""
Echo a snapshot of live network metadata.
"""
diff --git a/examples/finnegans_wake_demo/finnegans-wake-federated.py b/examples/finnegans_wake_demo/finnegans-wake-federated.py
index 60233ddb6..ff61a4ec5 100644
--- a/examples/finnegans_wake_demo/finnegans-wake-federated.py
+++ b/examples/finnegans_wake_demo/finnegans-wake-federated.py
@@ -44,17 +44,6 @@ from nucypher.network.middleware import RestMiddleware
def simpleObserver(event):
print(event)
-
-# Setup logging
-# root = Logger()
-# root.setLevel(LogLevel.debug)
-#
-# ch = logging.StreamHandler(sys.stdout)
-# ch.setLevel(logging.INFO)
-# formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
-# ch.setFormatter(formatter)
-# root.addHandler(ch)
-
globalLogPublisher.addObserver(simpleObserver)
# Temporary storage area for demo
diff --git a/nucypher/__init__.py b/nucypher/__init__.py
index 4c571a3d2..f32ffae43 100644
--- a/nucypher/__init__.py
+++ b/nucypher/__init__.py
@@ -14,9 +14,6 @@ GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with nucypher. If not, see .
"""
-
-import sys
-
from nucypher.__about__ import (
__author__, __license__, __summary__, __title__, __version__, __copyright__, __email__, __url__
)
@@ -32,36 +29,3 @@ __all__ = [
from umbral.config import set_default_curve
set_default_curve()
-
-
-# Report to Sentry #
-####################
-
-from nucypher.config.constants import REPORT_TO_SENTRY, NUCYPHER_SENTRY_ENDPOINT, PACKAGE_NAME
-
-if not hasattr(sys, '_pytest_is_running'):
- try:
- import sentry_sdk
- from sentry_sdk.integrations.logging import LoggingIntegration
- except ImportError:
- if REPORT_TO_SENTRY is True:
- raise ImportError("Sentry")
- else:
- import logging
- sentry_logging = LoggingIntegration(
- level=logging.INFO, # Capture info and above as breadcrumbs
- event_level=logging.DEBUG # Send debug logs as events
- )
- sentry_sdk.init(
- dsn=NUCYPHER_SENTRY_ENDPOINT,
- integrations=[sentry_logging],
- release='{}@{}'.format(PACKAGE_NAME, __version__)
- )
-
-# Twisted Log Observer #
-########################
-
-from nucypher.utilities.logging import simpleObserver
-from twisted.logger import globalLogPublisher
-
-globalLogPublisher.addObserver(simpleObserver)
diff --git a/nucypher/config/constants.py b/nucypher/config/constants.py
index 8fa142488..2ded4d1e3 100644
--- a/nucypher/config/constants.py
+++ b/nucypher/config/constants.py
@@ -33,10 +33,7 @@ DEFAULT_CONFIG_ROOT = APP_DIR.user_data_dir
SeednodeMetadata = namedtuple('seednode', ['checksum_address', 'rest_host', 'rest_port'])
SEEDNODES = tuple()
-# Sentry (Set to False to disable sending Errors and Logs to NuCypher's Sentry.)
-REPORT_TO_SENTRY = True
-NUCYPHER_SENTRY_ENDPOINT = "https://d8af7c4d692e4692a455328a280d845e@sentry.io/1310685"
# CLI
-DEBUG = True
+NUCYPHER_SENTRY_ENDPOINT = "https://d8af7c4d692e4692a455328a280d845e@sentry.io/1310685"
KEYRING_PASSPHRASE_ENVVAR_KEY = 'NUCYPHER_KEYRING_PASSPHRASE'
diff --git a/nucypher/utilities/logging.py b/nucypher/utilities/logging.py
index 3e10c9d1e..b52ced6c6 100644
--- a/nucypher/utilities/logging.py
+++ b/nucypher/utilities/logging.py
@@ -14,32 +14,33 @@ GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with nucypher. If not, see .
"""
+
+
+from sentry_sdk import Client, capture_exception, add_breadcrumb
from twisted.logger import ILogObserver
from zope.interface import provider
-from nucypher.config.constants import REPORT_TO_SENTRY
+
+from nucypher.config.constants import NUCYPHER_SENTRY_ENDPOINT
@provider(ILogObserver)
def simpleObserver(event):
- print(event.get('log_format'))
+ message = '{} ({}): {}'.format(event.get('log_level').name.upper(),
+ event.get('log_namespace'),
+ event.get('log_format'))
+ print(message)
-if REPORT_TO_SENTRY:
- from twisted.python import log
- from sentry_sdk import Client, capture_exception, add_breadcrumb
- from nucypher.config.constants import NUCYPHER_SENTRY_ENDPOINT
-
+def logToSentry(event):
client = Client(dsn=NUCYPHER_SENTRY_ENDPOINT)
- def logToSentry(event):
+ # Handle Logs
+ if not event.get('isError') or 'failure' not in event:
+ add_breadcrumb(level=event.get('log_level').name,
+ message=event.get('log_format'),
+ category=event.get('log_namespace'))
+ return
- # Handle Logs
- if not event.get('isError') or 'failure' not in event:
- add_breadcrumb(event)
- return
-
- # Handle Failures
- f = event['failure']
- capture_exception((f.type, f.value, f.getTracebackObject()))
-
- log.addObserver(logToSentry)
+ # Handle Failures
+ f = event['failure']
+ capture_exception((f.type, f.value, f.getTracebackObject()))
diff --git a/tests/conftest.py b/tests/conftest.py
index 8b3dc0f29..59b8bd0ff 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -14,17 +14,15 @@ GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with nucypher. If not, see .
"""
-from twisted.logger import ILogObserver
+
+# Test Logger Configuration
from twisted.logger import globalLogPublisher
-from zope.interface import provider
-
-@provider(ILogObserver)
-def simpleObserver(event):
- print(event)
+from cli.main import NucypherClickConfig
+from nucypher.utilities.logging import simpleObserver
+NucypherClickConfig.log_to_sentry = False
globalLogPublisher.addObserver(simpleObserver)
-
"""NOTICE: Depends on fixture modules; do not delete"""
from .fixtures import *
@@ -40,16 +38,6 @@ def pytest_addoption(parser):
help="run tests even if they are marked as slow")
-def pytest_configure(config):
- import sys
- sys._pytest_is_running = True
-
-
-def pytest_unconfigure(config):
- import sys
- del sys._pytest_is_running
-
-
def pytest_collection_modifyitems(config, items):
if config.getoption("--runslow"):
# --runslow given in cli: do not skip slow tests