mirror of https://github.com/nucypher/nucypher.git
724 lines
26 KiB
Python
Executable File
724 lines
26 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
|
|
import asyncio
|
|
import logging
|
|
import random
|
|
import sys
|
|
|
|
import subprocess
|
|
from constant_sorrow import constants
|
|
|
|
from nucypher.blockchain.eth.actors import Miner
|
|
from nucypher.blockchain.eth.chains import Blockchain, TesterBlockchain
|
|
from nucypher.blockchain.eth.deployers import NucypherTokenDeployer, MinerEscrowDeployer, PolicyManagerDeployer
|
|
from nucypher.characters import Ursula
|
|
from nucypher.config.constants import DEFAULT_CONFIG_ROOT, DEFAULT_SIMULATION_PORT, \
|
|
DEFAULT_SIMULATION_REGISTRY_FILEPATH, DEFAULT_INI_FILEPATH, DEFAULT_REST_PORT, DEFAULT_DHT_PORT, DEFAULT_DB_NAME, \
|
|
BASE_DIR
|
|
from nucypher.config.metadata import write_node_metadata, collect_stored_nodes
|
|
from nucypher.config.parsers import parse_nucypher_ini_config, parse_running_modes
|
|
|
|
__version__ = '0.1.0-alpha.0'
|
|
|
|
BANNER = """
|
|
_
|
|
| |
|
|
_ __ _ _ ___ _ _ _ __ | |__ ___ _ __
|
|
| '_ \| | | |/ __| | | | '_ \| '_ \ / _ \ '__|
|
|
| | | | |_| | (__| |_| | |_) | | | | __/ |
|
|
|_| |_|\__,_|\___|\__, | .__/|_| |_|\___|_|
|
|
__/ | |
|
|
|___/|_|
|
|
|
|
version {}
|
|
|
|
""".format(__version__)
|
|
|
|
import os
|
|
import click
|
|
from twisted.internet import reactor
|
|
|
|
from nucypher.blockchain.eth.agents import MinerAgent, PolicyAgent, NucypherTokenAgent
|
|
from nucypher.utilities.blockchain import token_airdrop
|
|
from nucypher.config.utils import validate_nucypher_ini_config, initialize_configuration
|
|
|
|
|
|
root = logging.getLogger()
|
|
root.setLevel(logging.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)
|
|
|
|
|
|
class NucypherClickConfig:
|
|
|
|
def __init__(self):
|
|
|
|
self.config_filepath = DEFAULT_INI_FILEPATH # TODO
|
|
click.echo("Using configuration filepath {}".format(self.config_filepath))
|
|
|
|
# Set operating and run modes
|
|
operating_modes = parse_running_modes(filepath=self.config_filepath)
|
|
self.operating_mode = operating_modes.get('mode', 'decentralized')
|
|
self.simulation_mode = operating_modes.get('simulation', False)
|
|
if self.simulation_mode is True:
|
|
simulation_running = False
|
|
sim_registry_filepath = DEFAULT_SIMULATION_REGISTRY_FILEPATH
|
|
else:
|
|
simulation_running = constants.SIMULATION_DISABLED
|
|
sim_registry_filepath = constants.SIMULATION_DISABLED
|
|
|
|
self.simulation_running = simulation_running
|
|
self.sim_registry_filepath = sim_registry_filepath
|
|
self.sim_processes = list()
|
|
|
|
sim_mode = 'simulation' if self.simulation_mode else 'live'
|
|
click.echo("Running in {} {} mode".format(sim_mode, self.operating_mode))
|
|
|
|
self.payload = parse_nucypher_ini_config(filepath=self.config_filepath)
|
|
click.echo("Successfully parsed configuration file")
|
|
|
|
# Blockchain connection contract agency
|
|
self.blockchain = constants.NO_BLOCKCHAIN_CONNECTION
|
|
self.accounts = constants.NO_BLOCKCHAIN_CONNECTION
|
|
self.token_agent = constants.NO_BLOCKCHAIN_CONNECTION
|
|
self.miner_agent = constants.NO_BLOCKCHAIN_CONNECTION
|
|
self.policy_agent = constants.NO_BLOCKCHAIN_CONNECTION
|
|
|
|
def connect_to_blockchain(self):
|
|
"""Initialize all blockchain entities from parsed config values"""
|
|
|
|
self.blockchain = Blockchain.from_config(filepath=self.config_filepath)
|
|
self.accounts = self.blockchain.interface.w3.eth.accounts
|
|
|
|
#TODO: Exception handling here for key error when using incompadible operating mode
|
|
if self.payload['tester'] and self.payload['deploy']:
|
|
self.blockchain.interface.deployer_address = self.accounts[0]
|
|
|
|
def connect_to_contracts(self, simulation: bool=False):
|
|
"""Initialize contract agency and set them on config"""
|
|
|
|
if simulation is True:
|
|
self.blockchain.interface._registry._swap_registry(filepath=self.sim_registry_filepath)
|
|
|
|
self.token_agent = NucypherTokenAgent(blockchain=self.blockchain)
|
|
self.miner_agent = MinerAgent(token_agent=self.token_agent)
|
|
self.policy_agent = PolicyAgent(miner_agent=self.miner_agent)
|
|
|
|
@property
|
|
def rest_uri(self):
|
|
host, port = self.payload['rest_host'], self.payload['rest_port']
|
|
uri_template = "http://{}:{}"
|
|
return uri_template.format(host, port)
|
|
|
|
@property
|
|
def dht_uri(self):
|
|
host, port = self.payload['dht_host'], self.payload['dht_port']
|
|
uri_template = "http://{}:{}"
|
|
return uri_template.format(host, port)
|
|
|
|
|
|
uses_config = click.make_pass_decorator(NucypherClickConfig, ensure=True)
|
|
|
|
|
|
@click.group()
|
|
@click.option('--version', help="Prints the installed version.", is_flag=True)
|
|
@click.option('--verbose', help="Enable verbose mode.", is_flag=True)
|
|
@click.option('--config-file', help="Specify a custom config filepath.", type=click.Path(), default=DEFAULT_INI_FILEPATH)
|
|
@uses_config
|
|
def cli(config, verbose, version, config_file):
|
|
"""Configure and manage a nucypher nodes"""
|
|
|
|
validate_nucypher_ini_config(filepath=config_file)
|
|
|
|
click.echo(BANNER)
|
|
|
|
# Store config data
|
|
config.verbose = verbose
|
|
config.config_filepath = config_file
|
|
|
|
if config.verbose:
|
|
click.echo("Running in verbose mode...")
|
|
if version:
|
|
click.echo("Version {}".format(__version__))
|
|
|
|
|
|
@cli.command()
|
|
@click.argument('action')
|
|
@click.option('--config-file', help="Specify a custom .ini configuration filepath")
|
|
@click.option('--data-dir', help="Specify a custom runtime directory")
|
|
@uses_config
|
|
def config(config, action, config_file, data_dir):
|
|
"""Manage the nucypher .ini configuration file"""
|
|
|
|
if action == "validate":
|
|
validate_nucypher_ini_config(config_file)
|
|
|
|
if action == "init":
|
|
click.confirm("Initialize new nucypher configuration?", abort=True)
|
|
config_root = data_dir if data_dir else DEFAULT_CONFIG_ROOT
|
|
initialize_configuration(config_root=config_root)
|
|
click.echo("Created configuration files at {}".format(config_root))
|
|
|
|
|
|
@cli.command()
|
|
@click.argument('action', default='list', required=False)
|
|
@click.option('--address', help="The account to lock/unlock instead of the default")
|
|
@uses_config
|
|
def accounts(config, action, address):
|
|
"""Manage ethereum node accounts"""
|
|
|
|
if action == 'list':
|
|
for index, address in enumerate(config.accounts):
|
|
if index == 0:
|
|
row = 'etherbase | {}'.format(address)
|
|
else:
|
|
row = '{} ....... | {}'.format(index, address)
|
|
click.echo(row)
|
|
|
|
elif action == 'unlock':
|
|
# passphrase = click.prompt("Enter passphrase to unlock {}".format(address))
|
|
raise NotImplementedError
|
|
|
|
elif action == 'lock':
|
|
|
|
# click.confirm("Lock {}?".format(address))
|
|
raise NotImplementedError
|
|
|
|
elif action == 'balance':
|
|
raise NotImplementedError
|
|
|
|
|
|
@cli.command()
|
|
@click.argument('action', default='list', required=False)
|
|
@click.option('--address', help="Send rewarded tokens to a specific address, instead of the default.")
|
|
@click.option('--value', help="Stake value in the smallest denomination")
|
|
@click.option('--duration', help="Stake duration in periods")
|
|
@click.option('--index', help="A specific stake index to resume")
|
|
@uses_config
|
|
def stake(config, action, address, index, value, duration):
|
|
"""
|
|
Manage active and inactive node blockchain stakes.
|
|
|
|
Arguments
|
|
==========
|
|
|
|
action - Which action to perform; The choices are:
|
|
|
|
- list: List all stakes for this node
|
|
- info: Display info about a specific stake
|
|
- start: Start the staking daemon
|
|
- confirm-activity: Manually confirm-activity for the current period
|
|
- divide-stake: Divide an existing stake
|
|
|
|
value - The quantity of tokens to stake.
|
|
|
|
periods - The duration (in periods) of the stake.
|
|
|
|
Options
|
|
========
|
|
|
|
--wallet-address - A valid ethereum checksum address to use instead of the default
|
|
--stake-index - The zero-based stake index, or stake tag for this wallet-address
|
|
|
|
"""
|
|
|
|
config.connect_to_contracts()
|
|
|
|
if not address:
|
|
|
|
for index, address in enumerate(config.accounts):
|
|
if index == 0:
|
|
row = 'etherbase (0) | {}'.format(address)
|
|
else:
|
|
row = '{} .......... | {}'.format(index, address)
|
|
click.echo(row)
|
|
|
|
click.echo("Select ethereum address")
|
|
account_selection = click.prompt("Enter 0-{}".format(len(config.accounts)), type=int)
|
|
address = config.accounts[account_selection]
|
|
|
|
if action == 'list':
|
|
live_stakes = config.miner_agent.get_all_stakes(miner_address=address)
|
|
for index, stake_info in enumerate(live_stakes):
|
|
row = '{} | {}'.format(index, stake_info)
|
|
click.echo(row)
|
|
|
|
elif action == 'init':
|
|
click.confirm("Stage a new stake?", abort=True)
|
|
|
|
live_stakes = config.miner_agent.get_all_stakes(miner_address=address)
|
|
if len(live_stakes) > 0:
|
|
raise RuntimeError("There is an existing stake for {}".format(address))
|
|
|
|
# Value
|
|
balance = config.token_agent.get_balance(address=address)
|
|
click.echo("Current balance: {}".format(balance))
|
|
value = click.prompt("Enter stake value", type=int)
|
|
|
|
# Duration
|
|
message = "Minimum duration: {} | Maximum Duration: {}".format(constants.MIN_LOCKED_PERIODS,
|
|
constants.MAX_REWARD_PERIODS)
|
|
click.echo(message)
|
|
duration = click.prompt("Enter stake duration in days", type=int)
|
|
|
|
start_period = config.miner_agent.get_current_period()
|
|
end_period = start_period + duration
|
|
|
|
# Review
|
|
click.echo("""
|
|
|
|
| Staged Stake |
|
|
|
|
Node: {address}
|
|
Value: {value}
|
|
Duration: {duration}
|
|
Start Period: {start_period}
|
|
End Period: {end_period}
|
|
|
|
""".format(address=address,
|
|
value=value,
|
|
duration=duration,
|
|
start_period=start_period,
|
|
end_period=end_period))
|
|
|
|
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.miner_agent.deposit_tokens(amount=value, lock_periods=duration, sender_address=address)
|
|
|
|
proc_params = ['run_ursula']
|
|
processProtocol = UrsulaProcessProtocol(command=proc_params)
|
|
ursula_proc = reactor.spawnProcess(processProtocol, "nucypher-cli", proc_params)
|
|
|
|
elif action == 'resume':
|
|
"""Reconnect and resume an existing live stake"""
|
|
|
|
proc_params = ['run_ursula']
|
|
processProtocol = UrsulaProcessProtocol(command=proc_params)
|
|
ursula_proc = reactor.spawnProcess(processProtocol, "nucypher-cli", proc_params)
|
|
|
|
elif action == 'confirm-activity':
|
|
"""Manually confirm activity for the active period"""
|
|
|
|
stakes = config.miner_agent.get_all_stakes(miner_address=address)
|
|
if len(stakes) == 0:
|
|
raise RuntimeError("There are no active stakes for {}".format(address))
|
|
config.miner_agent.confirm_activity(node_address=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)
|
|
if len(stakes) == 0:
|
|
raise RuntimeError("There are no active stakes for {}".format(address))
|
|
|
|
if not index:
|
|
for selection_index, stake_info in enumerate(stakes):
|
|
click.echo("{} ....... {}".format(selection_index, stake_info))
|
|
index = click.prompt("Select a stake to divide", type=int)
|
|
|
|
target_value = click.prompt("Enter new target value", type=int)
|
|
extension = click.prompt("Enter number of periods to extend", type=int)
|
|
|
|
click.echo("""
|
|
Current Stake: {}
|
|
|
|
New target value {}
|
|
New end period: {}
|
|
|
|
""".format(stakes[index],
|
|
target_value,
|
|
target_value+extension))
|
|
|
|
click.confirm("Is this correct?", abort=True)
|
|
config.miner_agent.divide_stake(miner_address=address,
|
|
stake_index=index,
|
|
value=value,
|
|
periods=extension)
|
|
|
|
elif action == 'collect-reward':
|
|
"""Withdraw staking reward to the specified wallet address"""
|
|
# click.confirm("Send {} to {}?".format)
|
|
# config.miner_agent.collect_staking_reward(collector_address=address)
|
|
raise NotImplementedError
|
|
|
|
elif action == 'abort':
|
|
click.confirm("Are you sure you want to abort the staking process?", abort=True)
|
|
# os.kill(pid=NotImplemented)
|
|
raise NotImplementedError
|
|
|
|
|
|
|
|
@cli.command()
|
|
@click.argument('action')
|
|
@click.option('--federated-only', is_flag=True)
|
|
@click.option('--nodes', help="The number of nodes to simulate", type=int)
|
|
@uses_config
|
|
def simulate(config, action, nodes, federated_only):
|
|
"""
|
|
Simulate the nucypher blockchain network
|
|
|
|
Arguments
|
|
==========
|
|
|
|
action - Which action to perform; The choices are:
|
|
- start: Start a multi-process nucypher network simulation
|
|
- stop: Stop a running simulation gracefully
|
|
|
|
Options
|
|
========
|
|
|
|
--nodes - The quantity of nodes (processes) to execute during the simulation
|
|
--duration = The number of periods to run the simulation before termination
|
|
|
|
"""
|
|
|
|
if action == 'init':
|
|
|
|
# OK, Try connecting to the blockchain
|
|
click.echo("Connecting to provider endpoint")
|
|
config.connect_to_blockchain()
|
|
|
|
# Actual simulation setup logic
|
|
one_million_eth = 10 ** 6 * 10 ** 18
|
|
click.echo("Airdropping {} ETH to {} test accounts".format(one_million_eth, len(config.accounts)))
|
|
config.blockchain.ether_airdrop(amount=one_million_eth) # wei -> ether | 1 Million ETH
|
|
|
|
# Fin
|
|
click.echo("Blockchain initialized")
|
|
|
|
if action == 'deploy':
|
|
|
|
if config.simulation_running is True:
|
|
raise RuntimeError("Network simulation already running")
|
|
|
|
if not federated_only:
|
|
config.connect_to_blockchain()
|
|
|
|
click.confirm("Deploy all nucypher contracts to blockchain?", abort=True)
|
|
click.echo("Bootstrapping simulated blockchain network")
|
|
|
|
blockchain = TesterBlockchain.from_config()
|
|
|
|
# TODO: Enforce Saftey - ensure this is "fake" #
|
|
conditions = ()
|
|
assert True
|
|
|
|
# Parse addresses
|
|
etherbase, *everybody_else = blockchain.interface.w3.eth.accounts
|
|
|
|
# Deploy contracts
|
|
token_deployer = NucypherTokenDeployer(blockchain=blockchain, deployer_address=etherbase)
|
|
token_deployer.arm()
|
|
token_deployer.deploy()
|
|
token_agent = token_deployer.make_agent()
|
|
click.echo("Deployed {}:{}".format(token_agent.contract_name, token_agent.contract_address))
|
|
|
|
miner_escrow_deployer = MinerEscrowDeployer(token_agent=token_agent, deployer_address=etherbase)
|
|
miner_escrow_deployer.arm()
|
|
miner_escrow_deployer.deploy()
|
|
miner_agent = miner_escrow_deployer.make_agent()
|
|
click.echo("Deployed {}:{}".format(miner_agent.contract_name, miner_agent.contract_address))
|
|
|
|
policy_manager_deployer = PolicyManagerDeployer(miner_agent=miner_agent, deployer_address=etherbase)
|
|
policy_manager_deployer.arm()
|
|
policy_manager_deployer.deploy()
|
|
policy_agent = policy_manager_deployer.make_agent()
|
|
click.echo("Deployed {}:{}".format(policy_agent.contract_name, policy_agent.contract_address))
|
|
|
|
airdrop_amount = 1000000 * int(constants.M)
|
|
click.echo("Airdropping tokens {} to {} addresses".format(airdrop_amount, len(everybody_else)))
|
|
_receipts = token_airdrop(token_agent=token_agent,
|
|
origin=etherbase,
|
|
addresses=everybody_else,
|
|
amount=airdrop_amount)
|
|
|
|
click.echo("Connecting to deployed contracts")
|
|
config.connect_to_contracts()
|
|
|
|
# Commit the current state of deployment to a registry file.
|
|
click.echo("Writing filesystem registry")
|
|
_sim_registry_name = config.blockchain.interface._registry.commit(filepath=DEFAULT_SIMULATION_REGISTRY_FILEPATH)
|
|
|
|
# Fin
|
|
click.echo("Ready to simulate decentralized swarm.")
|
|
|
|
else:
|
|
click.echo("Ready to run federated swarm.")
|
|
|
|
elif action == 'swarm':
|
|
|
|
if not federated_only:
|
|
config.connect_to_blockchain()
|
|
config.connect_to_contracts(simulation=True)
|
|
|
|
localhost = 'localhost'
|
|
|
|
# Select a port range to use on localhost for sim servers
|
|
start_port, stop_port = DEFAULT_SIMULATION_PORT, DEFAULT_SIMULATION_PORT + int(nodes)
|
|
port_range = range(start_port, stop_port)
|
|
click.echo("Selected local ports {}-{}".format(start_port, stop_port))
|
|
|
|
for index, sim_port_number in enumerate(port_range):
|
|
|
|
#
|
|
# Parse ursula parameters
|
|
#
|
|
|
|
rest_port, dht_port = sim_port_number, sim_port_number + 100
|
|
db_name = 'sim-{}'.format(rest_port)
|
|
|
|
cli_exec = os.path.join(BASE_DIR, 'cli', 'main.py')
|
|
python_exec = 'python'
|
|
|
|
proc_params = '''
|
|
python3 {} run_ursula --host {} --rest-port {} --dht-port {} --db-name {}
|
|
'''.format(python_exec, cli_exec, localhost, rest_port, dht_port, db_name).split()
|
|
|
|
if federated_only:
|
|
click.echo("Setting federated operating mode")
|
|
proc_params.append('--federated-only')
|
|
else:
|
|
sim_address = config.accounts[index+1]
|
|
miner = Miner(miner_agent=config.miner_agent, checksum_address=sim_address)
|
|
|
|
# stake a random amount
|
|
min_stake, balance = constants.MIN_ALLOWED_LOCKED, miner.token_balance
|
|
value = random.randint(min_stake, balance)
|
|
|
|
# for a random lock duration
|
|
min_locktime, max_locktime = constants.MIN_LOCKED_PERIODS, constants.MAX_MINTING_PERIODS
|
|
periods = random.randint(min_locktime, max_locktime)
|
|
|
|
miner.initialize_stake(amount=value, lock_periods=periods)
|
|
click.echo("{} Initialized new stake: {} tokens for {} periods".format(sim_address, value, periods))
|
|
|
|
proc_params.extend('--checksum-address {}'.format(sim_address).split())
|
|
|
|
# Spawn
|
|
click.echo("Spawning node #{}".format(index+1))
|
|
processProtocol = UrsulaProcessProtocol(command=proc_params)
|
|
cli_exec = os.path.join(BASE_DIR, 'cli', 'main.py')
|
|
ursula_proc = reactor.spawnProcess(processProtocol, cli_exec, proc_params)
|
|
config.sim_processes.append(ursula_proc)
|
|
|
|
#
|
|
# post-spawnProcess
|
|
#
|
|
|
|
# Start with some basic status data, then build on it
|
|
|
|
rest_uri = "http://{}:{}".format(localhost, rest_port)
|
|
dht_uri = "http://{}:{}".format(localhost, dht_port)
|
|
|
|
sim_data = "Started simulated Ursula | ReST {} | DHT {} ".format(rest_uri, dht_uri)
|
|
rest_uri = "{host}:{port}".format(host=localhost, port=str(sim_port_number))
|
|
dht_uri = '{host}:{port}'.format(host=localhost, port=dht_port)
|
|
sim_data.format(rest_uri, dht_uri)
|
|
|
|
if not federated_only:
|
|
stake_infos = tuple(config.miner_agent.get_all_stakes(miner_address=sim_address))
|
|
sim_data += '| ETH address {}'.format(sim_address)
|
|
sim_data += '| {} Active stakes '.format(len(stake_infos))
|
|
|
|
click.echo(sim_data)
|
|
config.simulation_running = True
|
|
|
|
click.echo("Starting the reactor")
|
|
try:
|
|
reactor.run()
|
|
|
|
finally:
|
|
|
|
if config.operating_mode == 'decentralized':
|
|
click.echo("Removing simulation registry")
|
|
os.remove(config.sim_registry_filepath)
|
|
|
|
click.echo("Stopping simulated Ursula processes")
|
|
for process in config.sim_processes:
|
|
os.kill(process.pid, 9)
|
|
click.echo("Killed {}".format(process))
|
|
config.simulation_running = False
|
|
click.echo("Simulation stopped")
|
|
|
|
elif action == 'stop':
|
|
# Kill the simulated ursulas TODO: read PIDs from storage?
|
|
if config.simulation_running is not True:
|
|
raise RuntimeError("Network simulation is not running")
|
|
|
|
for process in config.ursula_processes:
|
|
process.transport.signalProcess('KILL')
|
|
else:
|
|
# TODO: Confirm they are dead
|
|
config.simulation_running = False
|
|
|
|
elif action == 'status':
|
|
|
|
if not config.simulation_running:
|
|
status_message = "Simulation not running."
|
|
else:
|
|
|
|
ursula_processes = len(config.ursula_processes)
|
|
|
|
status_message = """
|
|
|
|
| Node Swarm Simulation Status |
|
|
|
|
Simulation processes .............. {}
|
|
|
|
""".format(ursula_processes)
|
|
|
|
click.echo(status_message)
|
|
|
|
elif action == 'demo':
|
|
"""Run the finnegans wake demo"""
|
|
demo_exec = os.path.join(BASE_DIR, 'cli', 'demos', 'finnegans-wake-demo.py')
|
|
process_args = [sys.executable, demo_exec]
|
|
|
|
if federated_only:
|
|
process_args.append('--federated-only')
|
|
|
|
subprocess.run(process_args, stdout=subprocess.PIPE)
|
|
|
|
|
|
@cli.command()
|
|
@click.option('--provider', help="Echo blockchain provider info", is_flag=True)
|
|
@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, provider, contracts, network):
|
|
"""
|
|
Echo a snapshot of live network metadata.
|
|
"""
|
|
|
|
provider_payload = """
|
|
|
|
| {chain_type} Interface |
|
|
|
|
Status ................... {connection}
|
|
Provider Type ............ {provider_type}
|
|
Etherbase ................ {etherbase}
|
|
Local Accounts ........... {accounts}
|
|
|
|
""".format(chain_type=config.blockchain.__class__.__name__,
|
|
connection='Connected' if config.blockchain.interface.is_connected else 'No Connection',
|
|
provider_type=config.blockchain.interface.provider_type,
|
|
etherbase=config.accounts[0],
|
|
accounts=len(config.accounts))
|
|
|
|
contract_payload = """
|
|
|
|
| NuCypher ETH Contracts |
|
|
|
|
Registry Path ............ {registry_filepath}
|
|
NucypherToken ............ {token}
|
|
MinerEscrow .............. {escrow}
|
|
PolicyManager ............ {manager}
|
|
|
|
""".format(registry_filepath=config.blockchain.interface.registry_filepath,
|
|
token=config.token_agent.contract_address,
|
|
escrow=config.miner_agent.contract_address,
|
|
manager=config.policy_agent.contract_address,
|
|
period=config.miner_agent.get_current_period())
|
|
|
|
network_payload = """
|
|
|
|
| Blockchain Network |
|
|
|
|
Current Period ........... {period}
|
|
Active Staking Ursulas ... {ursulas}
|
|
|
|
| Swarm |
|
|
|
|
Known Nodes ..............
|
|
Verified Nodes ...........
|
|
Phantom Nodes ............ NotImplemented
|
|
|
|
|
|
""".format(period=config.miner_agent.get_current_period(),
|
|
ursulas=config.miner_agent.get_miner_population())
|
|
|
|
subpayloads = ((provider, provider_payload),
|
|
(contracts, contract_payload),
|
|
(network, network_payload),
|
|
)
|
|
|
|
if not any(sp[0] for sp in subpayloads):
|
|
payload = ''.join(sp[1] for sp in subpayloads)
|
|
else:
|
|
payload = str()
|
|
for requested, subpayload in subpayloads:
|
|
if requested is True:
|
|
payload += subpayload
|
|
|
|
click.echo(payload)
|
|
|
|
|
|
@cli.command()
|
|
@click.option('--federated-only', is_flag=True, default=False)
|
|
@click.option('--seed-node', is_flag=True, default=False)
|
|
@click.option('--rest-port', type=int, default=DEFAULT_REST_PORT)
|
|
@click.option('--dht-port', type=int, default=DEFAULT_DHT_PORT)
|
|
@click.option('--db-name', type=str, default=DEFAULT_DB_NAME)
|
|
@click.option('--checksum-address', type=str)
|
|
@click.option('--data-dir', type=click.Path(), default=DEFAULT_CONFIG_ROOT)
|
|
@click.option('--config-file', type=click.Path(), default=DEFAULT_INI_FILEPATH)
|
|
def run_ursula(rest_port, dht_port, db_name,
|
|
checksum_address, federated_only,
|
|
seed_node, data_dir, config_file) -> None:
|
|
"""
|
|
|
|
The following procedure is required to "spin-up" an Ursula node.
|
|
|
|
1. Collect all known known from storages
|
|
2. Start the asyncio event loop
|
|
3. Initialize Ursula object
|
|
4. Start DHT listener
|
|
5. Enter the learning loop
|
|
6. Run TLS deployment
|
|
7. Start the staking daemon
|
|
|
|
Configurable values are first read from the .ini configuration file,
|
|
but can be overridden (mostly for testing purposes) with inline cli options.
|
|
|
|
"""
|
|
|
|
if not seed_node:
|
|
other_nodes = collect_stored_nodes(federated_only=federated_only) # 1. Collect known nodes
|
|
else:
|
|
other_nodes = tuple()
|
|
|
|
overrides = dict(federated_only=federated_only,
|
|
known_nodes=other_nodes,
|
|
rest_port=rest_port,
|
|
dht_port=dht_port,
|
|
db_name=db_name,
|
|
checksum_address=checksum_address)
|
|
|
|
asyncio.set_event_loop(asyncio.new_event_loop()) # 2. Init DHT async loop
|
|
|
|
# 3. Initialize Ursula (includes overrides)
|
|
ursula = Ursula.from_config(filepath=config_file, overrides=overrides)
|
|
|
|
ursula.dht_listen() # 4. Start DHT
|
|
|
|
write_node_metadata(seed_node=seed_node, node=ursula, node_metadata_dir=data_dir)
|
|
|
|
if not seed_node:
|
|
ursula.start_learning_loop() # 5. Enter learning loop
|
|
|
|
ursula.get_deployer().run() # 6. Run TLS Deployer
|
|
|
|
if not federated_only:
|
|
ursula.stake() # 7. start staking daemon
|
|
|
|
|
|
if __name__ == "__main__":
|
|
cli()
|