If at first you don't succeed (to connect to the Teacher node), try try again (and catch the appropriate errors).

pull/547/head
jMyles 2018-11-11 09:16:11 -08:00
parent 17d0a245da
commit 11f9a5d035
1 changed files with 65 additions and 40 deletions

View File

@ -18,27 +18,27 @@ You should have received a copy of the GNU General Public License
along with nucypher. If not, see <https://www.gnu.org/licenses/>.
"""
from ipaddress import ip_address
import collections
import hashlib
import json
import os
import shutil
import socket
from ipaddress import ip_address
from typing import Tuple, ClassVar
from urllib.parse import urlparse
import click
import requests
import time
from constant_sorrow.constants import NO_NODE_CONFIGURATION, NO_BLOCKCHAIN_CONNECTION
from eth_utils import is_checksum_address
from nacl.exceptions import CryptoError
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
from nacl.exceptions import CryptoError
import nucypher
from nucypher.blockchain.eth.agents import MinerAgent, PolicyAgent, NucypherTokenAgent, EthereumContractAgent
@ -73,7 +73,6 @@ BANNER = """
class NucypherClickConfig:
__LOG_TO_SENTRY_ENVVAR = "NUCYPHER_SENTRY_LOGS"
__NUCYPHER_SENTRY_ENDPOINT = "https://d8af7c4d692e4692a455328a280d845e@sentry.io/1310685"
_KEYRING_PASSPHRASE_ENVVAR = "NUCYPHER_KEYRING_PASSPHRASE"
@ -90,7 +89,7 @@ class NucypherClickConfig:
import sentry_sdk
import logging
sentry_logging = LoggingIntegration(
level=logging.INFO, # Capture info and above as breadcrumbs
level=logging.INFO, # Capture info and above as breadcrumbs
event_level=logging.DEBUG # Send debug logs as events
)
sentry_sdk.init(
@ -134,7 +133,8 @@ class NucypherClickConfig:
node_configuration = configuration_class.from_configuration_file(filepath=filepath)
except FileNotFoundError:
if self.config_root:
node_configuration = configuration_class(temp=False, config_root=self.config_root, auto_initialize=False)
node_configuration = configuration_class(temp=False, config_root=self.config_root,
auto_initialize=False)
else:
node_configuration = configuration_class(federated_only=self.federated_only,
auto_initialize=False,
@ -169,7 +169,8 @@ class NucypherClickConfig:
def create_account(self, passphrase: str = None) -> str:
"""Creates a new local or hosted ethereum wallet"""
choice = click.prompt("Create a new Hosted or Local account?", default='hosted', type=click.STRING).strip().lower()
choice = click.prompt("Create a new Hosted or Local account?", default='hosted',
type=click.STRING).strip().lower()
if choice not in ('hosted', 'local'):
click.echo("Invalid Input")
raise click.Abort()
@ -190,7 +191,8 @@ class NucypherClickConfig:
raise click.BadParameter("Invalid choice; Options are hosted or local.")
return new_address
def _collect_pending_configuration_details(self, ursula: bool=False, force: bool = False, rest_host=None) -> PendingConfigurationDetails:
def _collect_pending_configuration_details(self, ursula: bool = False, force: bool = False,
rest_host=None) -> PendingConfigurationDetails:
# Defaults
passphrase = None
@ -225,11 +227,12 @@ class NucypherClickConfig:
details = self.PendingConfigurationDetails(passphrase=passphrase, wallet=generate_wallet,
signing=generate_encrypting_keys, tls=generate_tls_keys,
skip_keys=skip_all_key_generation, save_file=save_node_configuration_file)
skip_keys=skip_all_key_generation,
save_file=save_node_configuration_file)
return details
def create_new_configuration(self,
ursula: bool=False,
ursula: bool = False,
force: bool = False,
rest_host: str = None,
no_registry: bool = False):
@ -251,7 +254,8 @@ class NucypherClickConfig:
self.node_configuration.federated_only = self.federated_only
try:
pending_config = self._collect_pending_configuration_details(force=force, ursula=ursula, rest_host=rest_host)
pending_config = self._collect_pending_configuration_details(force=force, ursula=ursula,
rest_host=rest_host)
new_installation_path = self.node_configuration.initialize(passphrase=pending_config.passphrase,
wallet=pending_config.wallet,
encrypting=pending_config.signing,
@ -348,6 +352,7 @@ class IPv4Address(click.ParamType):
IPV4_ADDRESS = IPv4Address()
CHECKSUM_ADDRESS = ChecksumAddress()
########################################
@ -356,16 +361,20 @@ CHECKSUM_ADDRESS = ChecksumAddress()
#
@click.group()
@click.option('--version', help="Echo the CLI version", is_flag=True, callback=echo_version, expose_value=False, is_eager=True)
@click.option('--version', help="Echo the CLI version", is_flag=True, callback=echo_version, expose_value=False,
is_eager=True)
@click.option('-v', '--verbose', help="Specify verbosity level", count=True)
@click.option('--dev', help="Run in development mode", is_flag=True)
@click.option('--federated-only', help="Connect only to federated nodes", is_flag=True, default=True)
@click.option('--config-root', help="Custom configuration directory", type=click.Path())
@click.option('--config-file', help="Path to configuration file", type=click.Path(exists=True, dir_okay=False, file_okay=True, readable=True))
@click.option('--metadata-dir', help="Custom known metadata directory", type=click.Path(exists=True, dir_okay=True, file_okay=False, writable=True))
@click.option('--config-file', help="Path to configuration file",
type=click.Path(exists=True, dir_okay=False, file_okay=True, readable=True))
@click.option('--metadata-dir', help="Custom known metadata directory",
type=click.Path(exists=True, dir_okay=True, file_okay=False, writable=True))
@click.option('--provider-uri', help="Blockchain provider's URI", type=click.STRING)
@click.option('--compile/--no-compile', help="Compile solidity from source files", is_flag=True)
@click.option('--registry-filepath', help="Custom contract registry filepath", type=click.Path(exists=True, dir_okay=False, file_okay=True, readable=True))
@click.option('--registry-filepath', help="Custom contract registry filepath",
type=click.Path(exists=True, dir_okay=False, file_okay=True, readable=True))
@click.option('--deployer', help="Connect using a deployer's blockchain interface", is_flag=True)
@click.option('--poa', help="Inject POA middleware", is_flag=True)
@uses_config
@ -381,7 +390,6 @@ def cli(config,
deployer,
compile,
poa):
click.echo(BANNER)
# Store config data
@ -422,7 +430,7 @@ def configure(config,
"""Manage Ursula node system configuration"""
# Fetch Existing Configuration
config.get_node_configuration(configuration_class=UrsulaConfiguration, rest_host=rest_host)
config.get_node_configuration(configuration_class=UrsulaConfiguration, rest_host=rest_host)
if action == "destroy":
config.destroy_configuration()
@ -486,16 +494,20 @@ def accounts(config,
click.secho("Created new ETH address {}".format(new_address), fg='blue')
if click.confirm("Set new address as the node's keying default account?".format(new_address)):
config.blockchain.interface.w3.eth.defaultAccount = new_address
click.echo("{} is now the node's default account.".format(config.blockchain.interface.w3.eth.defaultAccount))
click.echo(
"{} is now the node's default account.".format(config.blockchain.interface.w3.eth.defaultAccount))
if action == 'set-default':
config.blockchain.interface.w3.eth.defaultAccount = checksum_address # TODO: is there a better way to do this?
config.blockchain.interface.w3.eth.defaultAccount = checksum_address # TODO: is there a better way to do this?
click.echo("{} is now the node's default account.".format(config.blockchain.interface.w3.eth.defaultAccount))
elif action == 'export':
keyring = NucypherKeyring(account=checksum_address)
click.confirm("Export local private key for {} to node's keyring: {}?".format(checksum_address, config.provider_uri), abort=True)
passphrase = click.prompt("Enter passphrase to decrypt account", type=click.STRING, hide_input=True, confirmation_prompt=True)
click.confirm(
"Export local private key for {} to node's keyring: {}?".format(checksum_address, config.provider_uri),
abort=True)
passphrase = click.prompt("Enter passphrase to decrypt account", type=click.STRING, hide_input=True,
confirmation_prompt=True)
keyring._export_wallet_to_node(blockchain=config.blockchain, passphrase=passphrase)
elif action == 'list':
@ -503,7 +515,8 @@ def accounts(config,
token_balance = config.token_agent.get_balance(address=checksum_address)
eth_balance = config.blockchain.interface.w3.eth.getBalance(checksum_address)
base_row_template = ' {address}\n Tokens: {tokens}\n ETH: {eth}\n '
row_template = ('\netherbase |'+base_row_template) if not index else '{index} ....... |'+base_row_template
row_template = (
'\netherbase |' + base_row_template) if not index else '{index} ....... |' + base_row_template
row = row_template.format(index=index, address=checksum_address, tokens=token_balance, eth=eth_balance)
click.secho(row, fg='blue')
@ -513,7 +526,8 @@ def accounts(config,
click.echo('No checksum_address supplied, Using the default {}'.format(checksum_address))
token_balance = config.token_agent.get_balance(address=checksum_address)
eth_balance = config.token_agent.blockchain.interface.w3.eth.getBalance(checksum_address)
click.secho("Balance of {} | Tokens: {} | ETH: {}".format(checksum_address, token_balance, eth_balance), fg='blue')
click.secho("Balance of {} | Tokens: {} | ETH: {}".format(checksum_address, token_balance, eth_balance),
fg='blue')
elif action == "transfer-tokens":
destination, amount = __collect_transfer_details(denomination='tokens')
@ -536,8 +550,10 @@ def accounts(config,
@cli.command()
@click.option('--checksum-address', type=CHECKSUM_ADDRESS)
@click.option('--value', help="Token value of stake", type=click.IntRange(min=MIN_ALLOWED_LOCKED, max=MIN_ALLOWED_LOCKED, clamp=False))
@click.option('--duration', help="Period duration of stake", type=click.IntRange(min=MIN_LOCKED_PERIODS, max=MAX_MINTING_PERIODS, clamp=False))
@click.option('--value', help="Token value of stake",
type=click.IntRange(min=MIN_ALLOWED_LOCKED, max=MIN_ALLOWED_LOCKED, clamp=False))
@click.option('--duration', help="Period duration of stake",
type=click.IntRange(min=MIN_LOCKED_PERIODS, max=MAX_MINTING_PERIODS, clamp=False))
@click.option('--index', help="A specific stake index to resume", type=click.INT)
@click.argument('action', default='list', required=False)
@uses_config
@ -672,7 +688,7 @@ def stake(config,
""".format(stakes[index],
target_value,
target_value+extension))
target_value + extension))
click.confirm("Is this correct?", abort=True)
config.miner_agent.divide_stake(miner_address=checksum_address,
@ -699,7 +715,8 @@ def stake(config,
@click.option('--contract-name', help="Deploy a single contract by name", type=click.STRING)
@click.option('--force', is_flag=True)
@click.option('--deployer-address', help="Deployer's checksum address", type=CHECKSUM_ADDRESS)
@click.option('--registry-outfile', help="Output path for new registry", type=click.Path(), default=NodeConfiguration.REGISTRY_SOURCE)
@click.option('--registry-outfile', help="Output path for new registry", type=click.Path(),
default=NodeConfiguration.REGISTRY_SOURCE)
@click.argument('action')
@uses_config
def deploy(config,
@ -814,7 +831,9 @@ def deploy(config,
try:
deployer_info = deployers[contract_name]
except KeyError:
click.secho("No such contract {}. Available contracts are {}".format(contract_name, available_deployers), fg='red', bold=True)
click.secho(
"No such contract {}. Available contracts are {}".format(contract_name, available_deployers),
fg='red', bold=True)
raise click.Abort()
else:
_txs, _agent = __deploy_contract(deployer_info.deployer_class,
@ -838,7 +857,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: Save Txhashes
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')
@ -908,7 +927,7 @@ def status(config):
# Heading
label = "Known Nodes (connected {} / seen {})".format(number_of_known_nodes, seen_nodes)
heading = '\n'+label+" "*(45-len(label))+"Last Seen "
heading = '\n' + label + " " * (45 - len(label)) + "Last Seen "
click.secho(heading, bold=True, nl=False)
# Legend
@ -968,6 +987,7 @@ def ursula(config,
4. Run TLS deployment (Learning Loop + Reactor)
"""
log = Logger("ursula/launch")
password = os.environ.get(config._KEYRING_PASSPHRASE_ENVVAR, None)
if not password:
@ -978,7 +998,7 @@ def ursula(config,
raise click.BadArgumentUsage("No Configuration file found, and no --checksum address <addr> was provided.")
if not checksum_address and not config.dev:
raise click.BadOptionUsage(message="No account specified. pass --checksum-address, --dev, "
"or use a configuration file with --config-file <path>")
"or use a configuration file with --config-file <path>")
return UrsulaConfiguration(temp=config.dev,
auto_initialize=config.dev,
@ -1033,13 +1053,18 @@ def ursula(config,
raise click.BadParameter("Invalid teacher URI. Is the hostname prefixed with 'https://' ?")
port = parsed_teacher_uri.port or UrsulaConfiguration.DEFAULT_REST_PORT
teacher = Ursula.from_seed_and_stake_info(host=parsed_teacher_uri.hostname,
port=port,
federated_only=ursula_config.federated_only,
checksum_address=checksum_address,
minimum_stake=min_stake,
certificates_directory=ursula_config.known_certificates_dir)
teacher_nodes.append(teacher)
while not teacher_nodes:
try:
teacher = Ursula.from_seed_and_stake_info(host=parsed_teacher_uri.hostname,
port=port,
federated_only=ursula_config.federated_only,
checksum_address=checksum_address,
minimum_stake=min_stake,
certificates_directory=ursula_config.known_certificates_dir)
teacher_nodes.append(teacher)
except (socket.gaierror, requests.exceptions.ConnectionError, ConnectionRefusedError):
log.warn("Can't connect to seed node. Will retry.")
time.sleep(5)
#
# Produce