Integrate FleetState into NodeConfiguration; checksum_address -> checksum_public_address

pull/562/head
Kieran Prasch 2018-11-25 20:41:31 -08:00
parent 9a83488281
commit 1c86a07a13
No known key found for this signature in database
GPG Key ID: 199AB839D4125A62
15 changed files with 288 additions and 285 deletions

View File

@ -70,7 +70,7 @@ class Character(Learner):
is_me: bool = True,
federated_only: bool = False,
blockchain: Blockchain = None,
checksum_address: bytes = NO_BLOCKCHAIN_CONNECTION.bool_value(False),
checksum_public_address: bytes = NO_BLOCKCHAIN_CONNECTION.bool_value(False),
network_middleware: RestMiddleware = None,
keyring_dir: str = None,
crypto_power: CryptoPower = None,
@ -118,7 +118,7 @@ class Character(Learner):
else:
self._crypto_power = CryptoPower(power_ups=self._default_crypto_powerups)
self._checksum_address = checksum_address
self._checksum_address = checksum_public_address
#
# Self-Character
#
@ -161,10 +161,10 @@ class Character(Learner):
# Decentralized
#
if not federated_only:
if not checksum_address:
raise ValueError("No checksum_address provided while running in a non-federated mode.")
if not checksum_public_address:
raise ValueError("No checksum_public_address provided while running in a non-federated mode.")
else:
self._checksum_address = checksum_address # TODO: Check that this matches BlockchainPower
self._checksum_address = checksum_public_address # TODO: Check that this matches BlockchainPower
#
# Federated
#
@ -173,11 +173,11 @@ class Character(Learner):
self._set_checksum_address() # type: str
except NoSigningPower:
self._checksum_address = NO_BLOCKCHAIN_CONNECTION
if checksum_address:
if checksum_public_address:
# We'll take a checksum address, as long as it matches their singing key
if not checksum_address == self.checksum_public_address:
if not checksum_public_address == self.checksum_public_address:
error = "Federated-only Characters derive their address from their Signing key; got {} instead."
raise self.SuspiciousActivity(error.format(checksum_address))
raise self.SuspiciousActivity(error.format(checksum_public_address))
#
# Nicknames
@ -254,7 +254,7 @@ class Character(Learner):
with the public_material_bytes, and the resulting CryptoPowerUp instance
consumed by the Character.
# TODO: Need to be federated only until we figure out the best way to get the checksum_address in here.
# TODO: Need to be federated only until we figure out the best way to get the checksum_public_address in here.
"""

View File

@ -20,10 +20,12 @@ from collections import OrderedDict
import maya
import requests
import socket
import time
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.asymmetric.ec import EllipticCurve
from cryptography.hazmat.primitives.serialization import Encoding
from cryptography.x509 import load_pem_x509_certificate, Certificate
from cryptography.x509 import load_pem_x509_certificate, Certificate, NameOID
from eth_utils import to_checksum_address
from functools import partial
from twisted.internet import threads
@ -33,17 +35,20 @@ from typing import List
from bytestring_splitter import BytestringSplitter, VariableLengthBytestring
from constant_sorrow import constants
from constant_sorrow.constants import PUBLIC_ONLY
from nucypher.blockchain.eth.actors import PolicyAuthor, Miner
from nucypher.blockchain.eth.agents import MinerAgent
from nucypher.characters.base import Character, Learner
from nucypher.config.storages import NodeStorage
from nucypher.config.storages import NodeStorage, ForgetfulNodeStorage
from nucypher.crypto.api import keccak_digest
from nucypher.crypto.constants import PUBLIC_ADDRESS_LENGTH, PUBLIC_KEY_LENGTH
from nucypher.crypto.powers import SigningPower, EncryptingPower, DelegatingPower, BlockchainPower
from nucypher.keystore.keypairs import HostingKeypair
from nucypher.network.middleware import RestMiddleware
from nucypher.network.nodes import Teacher
from nucypher.network.protocols import InterfaceInfo
from nucypher.network.protocols import InterfaceInfo, parse_node_uri
from nucypher.network.server import ProxyRESTServer, TLSHostingPower, ProxyRESTRoutes
from nucypher.utilities.decorators import validate_checksum_address
from umbral.keys import UmbralPublicKey
from umbral.signing import Signature
@ -54,11 +59,11 @@ class Alice(Character, PolicyAuthor):
def __init__(self, is_me=True, federated_only=False, network_middleware=None, *args, **kwargs) -> None:
policy_agent = kwargs.pop("policy_agent", None)
checksum_address = kwargs.pop("checksum_address", None)
checksum_address = kwargs.pop("checksum_public_address", None)
Character.__init__(self,
is_me=is_me,
federated_only=federated_only,
checksum_address=checksum_address,
checksum_public_address=checksum_address,
network_middleware=network_middleware,
*args, **kwargs)
@ -455,7 +460,7 @@ class Ursula(Teacher, Character, Miner):
timestamp=None,
# Blockchain
checksum_address: str = None,
checksum_public_address: str = None,
# Character
password: str = None,
@ -475,7 +480,7 @@ class Ursula(Teacher, Character, Miner):
self._work_orders = list()
Character.__init__(self,
is_me=is_me,
checksum_address=checksum_address,
checksum_public_address=checksum_public_address,
start_learning_now=start_learning_now,
federated_only=federated_only,
crypto_power=crypto_power,
@ -493,7 +498,7 @@ class Ursula(Teacher, Character, Miner):
# Staking Ursula
#
if not federated_only:
Miner.__init__(self, is_me=is_me, checksum_address=checksum_address)
Miner.__init__(self, is_me=is_me, checksum_address=checksum_public_address)
# Access staking node via node's transacting keys TODO: Better handle ephemeral staking self ursula
blockchain_power = BlockchainPower(blockchain=self.blockchain, account=self.checksum_public_address)
@ -602,7 +607,7 @@ class Ursula(Teacher, Character, Miner):
return deployer
def rest_server_certificate(self): # TODO: relocate and use reference on TLS hosting power
return self.get_deployer().cert.to_cryptography()
return self.certificate
def __bytes__(self):
@ -627,6 +632,124 @@ class Ursula(Teacher, Character, Miner):
# Alternate Constructors
#
@classmethod
def from_rest_url(cls,
network_middleware: RestMiddleware,
host: str,
port: int,
certificate_filepath,
federated_only: bool = False,
*args, **kwargs
):
response = network_middleware.node_information(host, port, certificate_filepath=certificate_filepath)
if not response.status_code == 200:
raise RuntimeError("Got a bad response: {}".format(response))
stranger_ursula_from_public_keys = cls.from_bytes(response.content, federated_only=federated_only, *args, **kwargs)
return stranger_ursula_from_public_keys
@classmethod
def from_seednode_metadata(cls,
seednode_metadata,
*args,
**kwargs):
"""
Essentially another deserialization method, but this one doesn't reconstruct a complete
node from bytes; instead it's just enough to connect to and verify a node.
"""
return cls.from_seed_and_stake_info(checksum_public_address=seednode_metadata.checksum_public_address,
host=seednode_metadata.rest_host,
port=seednode_metadata.rest_port,
*args, **kwargs)
@classmethod
def from_teacher_uri(cls,
federated_only: bool,
teacher_uri: str,
min_stake: int,
) -> 'Ursula':
hostname, port, checksum_address = parse_node_uri(uri=teacher_uri)
try:
teacher = cls.from_seed_and_stake_info(host=hostname,
port=port,
federated_only=federated_only,
checksum_public_address=checksum_address,
minimum_stake=min_stake)
except (socket.gaierror, requests.exceptions.ConnectionError, ConnectionRefusedError):
# self.log.warn("Can't connect to seed node. Will retry.")
time.sleep(5) # TODO: Move this 5
else:
return teacher
@classmethod
@validate_checksum_address
def from_seed_and_stake_info(cls,
host: str,
port: int,
federated_only: bool,
minimum_stake: int = 0,
checksum_public_address: str = None,
network_middleware: RestMiddleware = None,
*args,
**kwargs
) -> 'Ursula':
#
# WARNING: xxx Poison xxx
# Let's learn what we can about the ... "seednode".
#
if network_middleware is None:
network_middleware = RestMiddleware()
# Fetch the hosts TLS certificate and read the common name
certificate = network_middleware.get_certificate(host=host, port=port)
real_host = certificate.subject.get_attributes_for_oid(NameOID.COMMON_NAME)[0].value
temp_node_storage = ForgetfulNodeStorage(federated_only=federated_only)
certificate_filepath = temp_node_storage.store_host_certificate(host=real_host,
certificate=certificate)
# Load the host as a potential seed node
potential_seed_node = cls.from_rest_url(
host=real_host,
port=port,
network_middleware=network_middleware,
certificate_filepath=certificate_filepath,
federated_only=True,
*args,
**kwargs) # TODO: 466
if checksum_public_address:
# Ensure this is the specific node we expected
if not checksum_public_address == potential_seed_node.checksum_public_address:
template = "This seed node has a different wallet address: {} (expected {}). Are you sure this is a seednode?"
raise potential_seed_node.SuspiciousActivity(template.format(potential_seed_node.checksum_public_address,
checksum_public_address))
# Check the node's stake (optional)
if minimum_stake > 0:
# TODO: check the blockchain to verify that address has more then minimum_stake. #511
raise NotImplementedError("Stake checking is not implemented yet.")
# Verify the node's TLS certificate
try:
potential_seed_node.verify_node(
network_middleware=network_middleware,
accept_federated_only=federated_only,
certificate_filepath=certificate_filepath)
except potential_seed_node.InvalidNode:
raise # TODO: What if our seed node fails verification?
# OK - everyone get out
temp_node_storage.forget()
return potential_seed_node
@classmethod
def from_bytes(cls,
ursula_as_bytes: bytes,
@ -684,7 +807,7 @@ class Ursula(Teacher, Character, Miner):
},
interface_signature=signature,
timestamp=timestamp,
checksum_address=to_checksum_address(public_address),
checksum_public_address=to_checksum_address(public_address),
certificate=certificate,
rest_host=rest_info.host,
rest_port=rest_info.port,
@ -699,8 +822,8 @@ class Ursula(Teacher, Character, Miner):
node_storage: NodeStorage,
checksum_adress: str,
federated_only: bool = False) -> 'Ursula':
return node_storage.get(checksum_address=checksum_adress,
federated_only=federated_only)
return node_storage.get(checksum_address=checksum_adress, federated_only=federated_only)
#
# Properties
@ -722,7 +845,6 @@ class Ursula(Teacher, Character, Miner):
@property
def rest_app(self):
rest_app_on_server = self.rest_server.rest_app
if not rest_app_on_server:
m = "This Ursula doesn't have a REST app attached. If you want one, init with is_me and attach_server."
raise AttributeError(m)

View File

@ -58,7 +58,7 @@ class Vladimir(Ursula):
rest_port=target_ursula.rest_information()[0].port,
certificate=target_ursula.rest_server_certificate(),
network_middleware=cls.network_middleware,
checksum_address = cls.fraud_address,
checksum_public_address = cls.fraud_address,
######### Asshole.
timestamp=target_ursula._timestamp,
interface_signature=target_ursula._interface_signature_object,

View File

@ -26,7 +26,8 @@ from twisted.logger import globalLogPublisher
from constant_sorrow.constants import NO_BLOCKCHAIN_CONNECTION, NO_PASSWORD
from nucypher.blockchain.eth.constants import MIN_LOCKED_PERIODS, MAX_MINTING_PERIODS
from nucypher.cli.painting import BANNER, paint_configuration
from nucypher.characters.lawful import Ursula
from nucypher.cli.painting import BANNER, paint_configuration, paint_known_nodes, paint_contract_status
from nucypher.cli.protocol import UrsulaCommandProtocol
from nucypher.cli.types import (
EIP55_CHECKSUM_ADDRESS,
@ -37,8 +38,6 @@ from nucypher.cli.types import (
STAKE_DURATION
)
from nucypher.config.characters import UrsulaConfiguration
from nucypher.config.constants import SEEDNODES
from nucypher.network.nodes import Teacher
from nucypher.utilities.logging import (
logToSentry,
getTextFileObserver,
@ -127,80 +126,11 @@ def status(click_config, config_file):
ursula_config.connect_to_blockchain(provider_uri=ursula_config.provider_uri)
ursula_config.connect_to_contracts()
contract_payload = """
# Contracts
paint_contract_status(ursula_config=ursula_config, click_config=click_config)
| NuCypher ETH Contracts |
Provider URI ............. {provider_uri}
Registry Path ............ {registry_filepath}
NucypherToken ............ {token}
MinerEscrow .............. {escrow}
PolicyManager ............ {manager}
""".format(provider_uri=ursula_config.blockchain.interface.provider_uri,
registry_filepath=ursula_config.blockchain.interface.registry.filepath,
token=ursula_config.token_agent.contract_address,
escrow=ursula_config.miner_agent.contract_address,
manager=ursula_config.policy_agent.contract_address,
period=ursula_config.miner_agent.get_current_period())
click.secho(contract_payload)
network_payload = """
| Blockchain Network |
Current Period ........... {period}
Gas Price ................ {gas_price}
Active Staking Ursulas ... {ursulas}
""".format(period=click_config.miner_agent.get_current_period(),
gas_price=click_config.blockchain.interface.w3.eth.gasPrice,
ursulas=click_config.miner_agent.get_miner_population())
click.secho(network_payload)
#
# Known Nodes
#
# Gather Data
known_nodes = ursula_config.read_known_nodes()
known_certificates = ursula_config.node_storage.all(certificates_only=True, federated_only=ursula_config.federated_only)
number_of_known_nodes = len(known_nodes)
seen_nodes = len(known_certificates)
# Operating Mode
federated_only = ursula_config.federated_only
if federated_only:
click.secho("Configured in Federated Only mode", fg='green')
# Heading
label = "Known Nodes (connected {} / seen {})".format(number_of_known_nodes, seen_nodes)
heading = '\n' + label + " " * (45 - len(label)) + "Last Seen "
click.secho(heading, bold=True, nl=False)
# Legend
color_index = {
'self': 'yellow',
'known': 'white',
'seednode': 'blue'
}
for node_type, color in color_index.items():
click.secho('{0:<6} | '.format(node_type), fg=color, nl=False)
click.echo('\n')
seednode_addresses = list(bn.checksum_address for bn in SEEDNODES)
for node in known_nodes:
row_template = "{} | {} | {}"
node_type = 'known'
if node.checksum_public_address == ursula_config.checksum_address:
node_type = 'self'
row_template += ' ({})'.format(node_type)
if node.checksum_public_address in seednode_addresses:
node_type = 'seednode'
row_template += ' ({})'.format(node_type)
click.secho(row_template.format(node.checksum_public_address,
node.rest_url(),
node.timestamp), fg=color_index[node_type])
paint_known_nodes(ursula=ursula_config)
@nucypher_cli.command()
@ -301,7 +231,7 @@ def ursula(click_config,
rest_port=rest_port,
db_filepath=db_filepath,
federated_only=federated_only,
checksum_address=checksum_address,
checksum_public_address=checksum_address,
no_registry=federated_only or no_registry,
registry_filepath=registry_filepath,
provider_uri=provider_uri)
@ -324,7 +254,7 @@ def ursula(click_config,
poa=poa,
registry_filepath=registry_filepath,
provider_uri=provider_uri,
checksum_address=checksum_address,
checksum_public_address=checksum_address,
federated_only=federated_only,
rest_host=rest_host,
rest_port=rest_port,
@ -338,7 +268,7 @@ def ursula(click_config,
# poa = poa,
# registry_filepath = registry_filepath,
# provider_uri = provider_uri,
# checksum_address = checksum_address,
# checksum_public_address = checksum_public_address,
# federated_only = federated_only,
# rest_host = rest_host,
# rest_port = rest_port,
@ -365,14 +295,13 @@ def ursula(click_config,
#
teacher_nodes = list()
if teacher_uri:
node = Teacher.from_teacher_uri(teacher_uri=teacher_uri,
min_stake=min_stake,
federated_only=federated_only)
node = Ursula.from_teacher_uri(teacher_uri=teacher_uri, min_stake=min_stake, federated_only=federated_only)
teacher_nodes.append(node)
#
# Produce - Step 2
#
ursula = ursula_config.produce()
ursula = ursula_config.produce(known_nodes=teacher_nodes)
ursula_config.log.debug("Initialized Ursula {}".format(ursula), fg='green')
# GO!
@ -488,7 +417,7 @@ def stake(click_config,
if not checksum_address:
if config.accounts == NO_BLOCKCHAIN_CONNECTION:
if click_config.accounts == NO_BLOCKCHAIN_CONNECTION:
click.echo('No account found.')
raise click.Abort()

View File

@ -47,6 +47,23 @@ BANNER = """
# Paint
#
def build_fleet_state_status(ursula) -> str:
# Build FleetState status line
if ursula.known_nodes.checksum is not NO_KNOWN_NODES:
fleet_state_checksum = ursula.known_nodes.checksum[:7]
fleet_state_nickname = ursula.known_nodes.nickname
fleet_state_icon = ursula.known_nodes.icon
fleet_state = '{checksum}{nickname}{icon}'.format(icon=fleet_state_icon,
nickname=fleet_state_nickname,
checksum=fleet_state_checksum)
elif ursula.known_nodes.checksum is not NO_KNOWN_NODES:
fleet_state = 'No Known Nodes'
else:
fleet_state = 'Unknown'
return fleet_state
def paint_configuration(config_filepath: str) -> None:
json_config = UrsulaConfiguration._read_configuration_file(filepath=config_filepath)
click.secho("\n======== Ursula Configuration ======== \n", bold=True)
@ -68,15 +85,7 @@ def paint_node_status(ursula, start_time):
teacher = 'Current Teacher ..... {}'.format(ursula._current_teacher_node)
# Build FleetState status line
if ursula.known_nodes.checksum is not NO_KNOWN_NODES:
fleet_state_checksum = ursula.known_nodes.checksum[:7]
fleet_state_nickname = ursula.known_nodes.nickname
fleet_state_icon = ursula.known_nodes.icon
fleet_state = '{2} {1} ({0})'.format(fleet_state_checksum, fleet_state_nickname, fleet_state_icon)
elif ursula.known_nodes.checksum is not NO_KNOWN_NODES:
fleet_state = 'No Known Nodes'
else:
fleet_state = 'Unknown'
fleet_state = build_fleet_state_status(ursula=ursula)
stats = ['⇀URSULA {}'.format(ursula.nickname_icon),
'{}'.format(ursula),
@ -108,8 +117,13 @@ def paint_known_nodes(ursula) -> None:
# Heading
label = "Known Nodes (connected {} / seen {})".format(number_of_known_nodes, seen_nodes)
heading = '\n' + label + " " * (45 - len(label)) + "Last Seen "
click.secho(heading, bold=True, nl=False)
heading = '\n' + label + " " * (45 - len(label))
click.secho(heading, bold=True, nl=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)
# Legend
color_index = {
@ -117,14 +131,16 @@ def paint_known_nodes(ursula) -> None:
'known': 'white',
'seednode': 'blue'
}
for node_type, color in color_index.items():
click.secho('{0:<6} | '.format(node_type), fg=color, nl=False)
click.echo('\n')
# Ledgend
# for node_type, color in color_index.items():
# click.secho('{0:<6} | '.format(node_type), fg=color, nl=False)
# click.echo('\n')
seednode_addresses = list(bn.checksum_address for bn in SEEDNODES)
for node in known_nodes:
row_template = "{} | {} | {} | {} | {}"
row_template = "{} | {}"
node_type = 'known'
if node.checksum_public_address == ursula.checksum_public_address:
node_type = 'self'
@ -132,9 +148,37 @@ def paint_known_nodes(ursula) -> None:
elif node.checksum_public_address in seednode_addresses:
node_type = 'seednode'
row_template += ' ({})'.format(node_type)
click.secho(row_template.format(node.checksum_public_address,
node.rest_url().ljust(20),
node.nickname.ljust(50),
node.timestamp,
node.last_seen,
), fg=color_index[node_type])
click.secho(row_template.format(node.rest_url().ljust(20), node), fg=color_index[node_type])
def paint_contract_status(ursula_config, click_config):
contract_payload = """
| NuCypher ETH Contracts |
Provider URI ............. {provider_uri}
Registry Path ............ {registry_filepath}
NucypherToken ............ {token}
MinerEscrow .............. {escrow}
PolicyManager ............ {manager}
""".format(provider_uri=ursula_config.blockchain.interface.provider_uri,
registry_filepath=ursula_config.blockchain.interface.registry.filepath,
token=ursula_config.token_agent.contract_address,
escrow=ursula_config.miner_agent.contract_address,
manager=ursula_config.policy_agent.contract_address,
period=ursula_config.miner_agent.get_current_period())
click.secho(contract_payload)
network_payload = """
| Blockchain Network |
Current Period ........... {period}
Gas Price ................ {gas_price}
Active Staking Ursulas ... {ursulas}
""".format(period=click_config.miner_agent.get_current_period(),
gas_price=click_config.blockchain.interface.w3.eth.gasPrice,
ursulas=click_config.miner_agent.get_miner_population())
click.secho(network_payload)

View File

@ -24,6 +24,8 @@ import maya
from twisted.internet import reactor
from twisted.protocols.basic import LineReceiver
from nucypher.cli.painting import build_fleet_state_status
class UrsulaCommandProtocol(LineReceiver):
@ -39,12 +41,20 @@ class UrsulaCommandProtocol(LineReceiver):
# Expose Ursula functional entry points
self.__commands = {
'stop': reactor.stop,
'known_nodes': self.paintKnownNodes,
# Status
'status': self.paintStatus,
'known_nodes': self.paintKnownNodes,
'fleet_state': self.paintFleetState,
# Learning Control
'cycle_teacher': self.ursula.cycle_teacher_node,
'start_learning': self.ursula.start_learning_loop,
'stop_learning': self.ursula.stop_learning_loop
'stop_learning': self.ursula.stop_learning_loop,
# Process Control
'stop': reactor.stop,
}
super().__init__()
@ -61,6 +71,10 @@ class UrsulaCommandProtocol(LineReceiver):
from nucypher.cli.painting import paint_node_status
paint_node_status(ursula=self.ursula, start_time=self.start_time)
def paintFleetState(self):
line = '{}'.format(build_fleet_state_status(ursula=self.ursula))
click.secho(line)
def connectionMade(self):
message = 'Attached {}@{}'.format(

View File

@ -25,7 +25,7 @@ from nucypher.blockchain.eth.constants import MIN_ALLOWED_LOCKED, MAX_MINTING_PE
class ChecksumAddress(click.ParamType):
name = 'checksum_address'
name = 'checksum_public_address'
def convert(self, value, param, ctx):
if is_checksum_address(value):

View File

@ -34,5 +34,5 @@ DEFAULT_CONFIG_ROOT = APP_DIR.user_data_dir
USER_LOG_DIR = APP_DIR.user_log_dir
# Static Seednodes
SeednodeMetadata = namedtuple('seednode', ['checksum_address', 'rest_host', 'rest_port'])
SeednodeMetadata = namedtuple('seednode', ['checksum_public_address', 'rest_host', 'rest_port'])
SEEDNODES = tuple()

View File

@ -47,6 +47,7 @@ from nucypher.config.keyring import NucypherKeyring
from nucypher.config.storages import NodeStorage, ForgetfulNodeStorage, LocalFileBasedNodeStorage
from nucypher.crypto.powers import CryptoPowerUp, CryptoPower
from nucypher.network.middleware import RestMiddleware
from nucypher.network.nodes import FleetStateTracker
from umbral.signing import Signature
@ -99,7 +100,7 @@ class NodeConfiguration(ABC):
# Identity
is_me: bool = True,
checksum_address: str = None,
checksum_public_address: str = None,
crypto_power: CryptoPower = None,
# Keyring
@ -126,7 +127,7 @@ class NodeConfiguration(ABC):
# Node Storage
known_nodes: set = None,
node_storage: NodeStorage = None,
load_metadata: bool = True,
reload_metadata: bool = True,
save_metadata: bool = True,
# Blockchain
@ -195,11 +196,11 @@ class NodeConfiguration(ABC):
# Identity
#
self.is_me = is_me
self.checksum_address = checksum_address
self.checksum_public_address = checksum_public_address
if self.is_me is True or dev_mode is True:
# Self
if self.checksum_address and dev_mode is False:
if self.checksum_public_address and dev_mode is False:
self.attach_keyring()
self.network_middleware = network_middleware or self.__DEFAULT_NETWORK_MIDDLEWARE_CLASS()
else:
@ -214,12 +215,17 @@ class NodeConfiguration(ABC):
#
# Learner
#
self.known_nodes = known_nodes or set()
self.learn_on_same_thread = learn_on_same_thread
self.abort_on_learning_error = abort_on_learning_error
self.start_learning_now = start_learning_now
self.save_metadata = save_metadata
self.load_metadata = load_metadata
self.reload_metadata = reload_metadata
self.__fleet_state = FleetStateTracker()
known_nodes = known_nodes or set()
if known_nodes:
self.known_nodes._nodes.update({node.checksum_public_address: node for node in known_nodes})
self.known_nodes.record_fleet_state()
#
# Blockchain
@ -240,7 +246,7 @@ class NodeConfiguration(ABC):
# Ephemeral dev settings
self.save_metadata = False
self.load_metadata = False
self.reload_metadata = False
# Generate one-time alphanumeric development password
alphabet = string.ascii_letters + string.digits
@ -258,6 +264,10 @@ class NodeConfiguration(ABC):
def dev_mode(self):
return self.__dev_mode
@property
def known_nodes(self):
return self.__fleet_state
def connect_to_blockchain(self, provider_uri: str, poa: bool = False, compile_contracts: bool = False):
if self.federated_only:
raise NodeConfiguration.ConfigurationError("Cannot connect to blockchain in federated mode")
@ -278,7 +288,10 @@ class NodeConfiguration(ABC):
self.log.debug("Established connection to nucypher contracts")
def read_known_nodes(self):
self.known_nodes.update(self.node_storage.all(federated_only=self.federated_only))
known_nodes = self.node_storage.all(federated_only=self.federated_only)
known_nodes = {node.checksum_public_address: node for node in known_nodes}
self.known_nodes._nodes.update(known_nodes)
self.known_nodes.record_fleet_state()
return self.known_nodes
def forget_nodes(self) -> None:
@ -389,7 +402,7 @@ class NodeConfiguration(ABC):
# Identity
is_me=self.is_me,
federated_only=self.federated_only, # TODO: 466
checksum_address=self.checksum_address,
checksum_public_address=self.checksum_public_address,
keyring_dir=self.keyring_dir,
# Behavior
@ -403,8 +416,12 @@ class NodeConfiguration(ABC):
@property
def dynamic_payload(self, **overrides) -> dict:
"""Exported dynamic configuration values for initializing Ursula"""
if self.load_metadata:
self.known_nodes.update(self.node_storage.all(federated_only=self.federated_only))
if self.reload_metadata:
known_nodes = self.node_storage.all(federated_only=self.federated_only)
known_nodes = {node.checksum_public_address: node for node in known_nodes}
self.known_nodes._nodes.update(known_nodes)
self.known_nodes.record_fleet_state()
payload = dict(network_middleware=self.network_middleware or self.__DEFAULT_NETWORK_MIDDLEWARE_CLASS(),
known_nodes=self.known_nodes,
node_storage=self.node_storage,
@ -503,15 +520,15 @@ class NodeConfiguration(ABC):
def attach_keyring(self, checksum_address: str = None, *args, **kwargs) -> None:
if self.keyring is not NO_KEYRING_ATTACHED:
if self.keyring.checksum_address != (checksum_address or self.checksum_address):
if self.keyring.checksum_address != (checksum_address or self.checksum_public_address):
raise self.ConfigurationError("There is already a keyring attached to this configuration.")
return
if (checksum_address or self.checksum_address) is None:
if (checksum_address or self.checksum_public_address) is None:
raise self.ConfigurationError("No account specified to unlock keyring")
self.keyring = NucypherKeyring(keyring_root=self.keyring_dir, # type: str
account=checksum_address or self.checksum_address, # type: str
account=checksum_address or self.checksum_public_address, # type: str
*args, **kwargs)
def write_keyring(self,
@ -533,9 +550,9 @@ class NodeConfiguration(ABC):
# TODO: Operating mode switch #466
if self.federated_only or not wallet:
self.checksum_address = self.keyring.federated_address
self.checksum_public_address = self.keyring.federated_address
else:
self.checksum_address = self.keyring.checksum_address
self.checksum_public_address = self.keyring.checksum_address
return self.keyring

View File

@ -250,8 +250,7 @@ class Learner:
self.unresponsive_startup_nodes = list() # TODO: Attempt to use these again later
for node in known_nodes:
try:
self.remember_node(
node) # TODO: Need to test this better - do we ever init an Ursula-Learner with Node Storage?
self.remember_node(node) # TODO: Need to test this better - do we ever init an Ursula-Learner with Node Storage?
except self.UnresponsiveTeacher:
self.unresponsive_startup_nodes.append(node)
@ -285,7 +284,7 @@ class Learner:
def __attempt_seednode_learning(seednode_metadata, current_attempt=1):
from nucypher.characters.lawful import Ursula
self.log.debug(
"Seeding from: {}|{}:{}".format(seednode_metadata.checksum_address,
"Seeding from: {}|{}:{}".format(seednode_metadata.checksum_public_address,
seednode_metadata.rest_host,
seednode_metadata.rest_port))
@ -725,128 +724,6 @@ class Teacher:
Raise when a Character tries to use another Character as decentralized when the latter is federated_only.
"""
#
# Alternate Constructors
#
@classmethod
def from_seednode_metadata(cls,
seednode_metadata,
*args,
**kwargs):
"""
Essentially another deserialization method, but this one doesn't reconstruct a complete
node from bytes; instead it's just enough to connect to and verify a node.
"""
return cls.from_seed_and_stake_info(checksum_address=seednode_metadata.checksum_address,
host=seednode_metadata.rest_host,
port=seednode_metadata.rest_port,
*args, **kwargs)
@classmethod
def from_teacher_uri(cls,
federated_only: bool,
teacher_uri: str,
min_stake: int,
) -> 'Ursula':
hostname, port, checksum_address = parse_node_uri(uri=teacher_uri)
try:
teacher = cls.from_seed_and_stake_info(host=hostname,
port=port,
federated_only=federated_only,
checksum_address=checksum_address,
minimum_stake=min_stake)
except (socket.gaierror, requests.exceptions.ConnectionError, ConnectionRefusedError):
# self.log.warn("Can't connect to seed node. Will retry.")
time.sleep(5) # TODO: Move this 5
else:
return teacher
@classmethod
@validate_checksum_address
def from_seed_and_stake_info(cls,
host: str,
port: int,
federated_only: bool,
minimum_stake: int = 0,
checksum_address: str = None,
network_middleware: RestMiddleware = None,
*args,
**kwargs
) -> 'Teacher':
#
# WARNING: xxx Poison xxx
# Let's learn what we can about the ... "seednode".
#
if network_middleware is None:
network_middleware = RestMiddleware()
# Fetch the hosts TLS certificate and read the common name
certificate = network_middleware.get_certificate(host=host, port=port)
real_host = certificate.subject.get_attributes_for_oid(NameOID.COMMON_NAME)[0].value
temp_node_storage = ForgetfulNodeStorage(federated_only=federated_only)
certificate_filepath = temp_node_storage.store_host_certificate(host=real_host,
certificate=certificate)
# Load the host as a potential seed node
potential_seed_node = cls.from_rest_url(
host=real_host,
port=port,
network_middleware=network_middleware,
certificate_filepath=certificate_filepath,
federated_only=True,
*args,
**kwargs) # TODO: 466
if checksum_address:
# Ensure this is the specific node we expected
if not checksum_address == potential_seed_node.checksum_public_address:
template = "This seed node has a different wallet address: {} (expected {}). Are you sure this is a seednode?"
raise potential_seed_node.SuspiciousActivity(template.format(potential_seed_node.checksum_public_address,
checksum_address))
# Check the node's stake (optional)
if minimum_stake > 0:
# TODO: check the blockchain to verify that address has more then minimum_stake. #511
raise NotImplementedError("Stake checking is not implemented yet.")
# Verify the node's TLS certificate
try:
potential_seed_node.verify_node(
network_middleware=network_middleware,
accept_federated_only=federated_only,
certificate_filepath=certificate_filepath)
except potential_seed_node.InvalidNode:
raise # TODO: What if our seed node fails verification?
# OK - everyone get out
temp_node_storage.forget()
return potential_seed_node
@classmethod
def from_rest_url(cls,
network_middleware: RestMiddleware,
host: str,
port: int,
certificate_filepath,
federated_only: bool = False,
*args, **kwargs
):
response = network_middleware.node_information(host, port, certificate_filepath=certificate_filepath)
if not response.status_code == 200:
raise RuntimeError("Got a bad response: {}".format(response))
stranger_ursula_from_public_keys = cls.from_bytes(response.content, federated_only=federated_only, *args,
**kwargs)
return stranger_ursula_from_public_keys
@classmethod
def from_tls_hosting_power(cls, tls_hosting_power: TLSHostingPower, *args, **kwargs) -> 'Teacher':
certificate_filepath = tls_hosting_power.keypair.certificate_filepath

View File

@ -83,7 +83,7 @@ def make_decentralized_ursulas(ursula_config: UrsulaConfiguration,
ursulas = set()
for port, checksum_address in enumerate(ether_addresses, start=starting_port):
ursula = ursula_config.produce(checksum_address=checksum_address,
ursula = ursula_config.produce(checksum_public_address=checksum_address,
db_filepath=MOCK_URSULA_DB_FILEPATH,
rest_port=port + 100,
**ursula_overrides)

View File

@ -140,7 +140,7 @@ def test_alices_powers_are_persistent(federated_ursulas, tmpdir):
start_learning_now=False,
federated_only=True,
save_metadata=False,
load_metadata=False)
reload_metadata=False)
# Generate keys and write them the disk
alice_config.initialize(password=INSECURE_DEVELOPMENT_PASSWORD)

View File

@ -99,7 +99,7 @@ def test_character_blockchain_power(testerchain):
sig_privkey = testerchain.interface.providers[0].ethereum_tester.backend._key_lookup[eth_utils.to_canonical_address(eth_address)]
sig_pubkey = sig_privkey.public_key
signer = Character(is_me=True, checksum_address=eth_address)
signer = Character(is_me=True, checksum_public_address=eth_address)
signer._crypto_power.consume_power_up(BlockchainPower(testerchain, eth_address))
# Due to testing backend, the account is already unlocked.

View File

@ -59,6 +59,6 @@ def test_empty_federated_status(click_runner, custom_filepath):
assert result.exit_code == 0
assert 'Federated Only' in result.output
heading = 'Known Nodes (connected 0 / seen 0) Last Seen self | known | seednode |'
heading = 'Known Nodes (connected 0 / seen 0)'
assert heading in result.output
assert 'password' not in result.output

View File

@ -128,7 +128,7 @@ def ursula_federated_test_config():
federated_only=True,
network_middleware=MockRestMiddleware(),
save_metadata=False,
load_metadata=False)
reload_metadata=False)
yield ursula_config
ursula_config.cleanup()
@ -145,7 +145,7 @@ def ursula_decentralized_test_config(three_agents):
network_middleware=MockRestMiddleware(),
import_seed_registry=False,
save_metadata=False,
load_metadata=False)
reload_metadata=False)
yield ursula_config
ursula_config.cleanup()
@ -159,7 +159,7 @@ def alice_federated_test_config(federated_ursulas):
federated_only=True,
abort_on_learning_error=True,
save_metadata=False,
load_metadata=False)
reload_metadata=False)
yield config
config.cleanup()
@ -171,13 +171,13 @@ def alice_blockchain_test_config(blockchain_ursulas, three_agents):
config = AliceConfiguration(dev_mode=True,
is_me=True,
checksum_address=alice_address,
checksum_public_address=alice_address,
network_middleware=MockRestMiddleware(),
known_nodes=blockchain_ursulas,
abort_on_learning_error=True,
import_seed_registry=False,
save_metadata=False,
load_metadata=False)
reload_metadata=False)
yield config
config.cleanup()
@ -190,7 +190,7 @@ def bob_federated_test_config():
abort_on_learning_error=True,
federated_only=True,
save_metadata=False,
load_metadata=False)
reload_metadata=False)
yield config
config.cleanup()
@ -201,7 +201,7 @@ def bob_blockchain_test_config(blockchain_ursulas, three_agents):
etherbase, alice_address, bob_address, *everyone_else = token_agent.blockchain.interface.w3.eth.accounts
config = BobConfiguration(dev_mode=True,
checksum_address=bob_address,
checksum_public_address=bob_address,
network_middleware=MockRestMiddleware(),
known_nodes=blockchain_ursulas,
start_learning_now=False,
@ -209,7 +209,7 @@ def bob_blockchain_test_config(blockchain_ursulas, three_agents):
federated_only=False,
import_seed_registry=False,
save_metadata=False,
load_metadata=False)
reload_metadata=False)
yield config
config.cleanup()