mirror of https://github.com/nucypher/nucypher.git
commit
13b564c53c
12
cli/main.py
12
cli/main.py
|
@ -1090,21 +1090,21 @@ def status(config,
|
||||||
color_index = {
|
color_index = {
|
||||||
'self': 'yellow',
|
'self': 'yellow',
|
||||||
'known': 'white',
|
'known': 'white',
|
||||||
'bootnode': 'blue'
|
'seednode': 'blue'
|
||||||
}
|
}
|
||||||
for node_type, color in color_index.items():
|
for node_type, color in color_index.items():
|
||||||
click.secho('{0:<6} | '.format(node_type), fg=color, nl=False)
|
click.secho('{0:<6} | '.format(node_type), fg=color, nl=False)
|
||||||
click.echo('\n')
|
click.echo('\n')
|
||||||
|
|
||||||
bootnode_addresses = list(bn.checksum_address for bn in BOOTNODES)
|
seednode_addresses = list(bn.checksum_address for bn in BOOTNODES)
|
||||||
for node in known_nodes:
|
for node in known_nodes:
|
||||||
row_template = "{} | {} | {}"
|
row_template = "{} | {} | {}"
|
||||||
node_type = 'known'
|
node_type = 'known'
|
||||||
if node.checksum_public_address == config.node_configuration.checksum_address:
|
if node.checksum_public_address == config.node_configuration.checksum_address:
|
||||||
node_type = 'self'
|
node_type = 'self'
|
||||||
row_template += ' ({})'.format(node_type)
|
row_template += ' ({})'.format(node_type)
|
||||||
if node.checksum_public_address in bootnode_addresses:
|
if node.checksum_public_address in seednode_addresses:
|
||||||
node_type = 'bootnode'
|
node_type = 'seednode'
|
||||||
row_template += ' ({})'.format(node_type)
|
row_template += ' ({})'.format(node_type)
|
||||||
click.secho(row_template.format(node.checksum_public_address,
|
click.secho(row_template.format(node.checksum_public_address,
|
||||||
node.rest_url(),
|
node.rest_url(),
|
||||||
|
@ -1196,8 +1196,8 @@ def ursula(config,
|
||||||
config.operating_mode = "federated" if ursula_config.federated_only else "decentralized"
|
config.operating_mode = "federated" if ursula_config.federated_only else "decentralized"
|
||||||
click.secho("Running in {} mode".format(config.operating_mode), fg='blue')
|
click.secho("Running in {} mode".format(config.operating_mode), fg='blue')
|
||||||
|
|
||||||
# Bootnodes, Seeds, Known Nodes
|
# seednodes, Seeds, Known Nodes
|
||||||
ursula_config.load_bootnodes()
|
ursula_config.load_seednodes()
|
||||||
quantity_known_nodes = len(ursula_config.known_nodes)
|
quantity_known_nodes = len(ursula_config.known_nodes)
|
||||||
if quantity_known_nodes > 0:
|
if quantity_known_nodes > 0:
|
||||||
click.secho("Loaded {} known nodes from storages".format(quantity_known_nodes, fg='blue'))
|
click.secho("Loaded {} known nodes from storages".format(quantity_known_nodes, fg='blue'))
|
||||||
|
|
|
@ -1,23 +1,24 @@
|
||||||
import random
|
import random
|
||||||
|
import time
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from collections import deque
|
from collections import deque
|
||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
from logging import Logger
|
from logging import Logger
|
||||||
from logging import getLogger
|
from logging import getLogger
|
||||||
from tempfile import TemporaryDirectory
|
from tempfile import TemporaryDirectory
|
||||||
|
from typing import Dict, ClassVar, Set
|
||||||
|
from typing import Tuple
|
||||||
|
from typing import Union, List
|
||||||
|
|
||||||
import maya
|
import maya
|
||||||
import requests
|
import requests
|
||||||
import time
|
|
||||||
from constant_sorrow import constants, default_constant_splitter
|
from constant_sorrow import constants, default_constant_splitter
|
||||||
from eth_keys import KeyAPI as EthKeyAPI
|
from eth_keys import KeyAPI as EthKeyAPI
|
||||||
from eth_utils import to_checksum_address, to_canonical_address
|
from eth_utils import to_checksum_address, to_canonical_address
|
||||||
from requests.exceptions import SSLError
|
from requests.exceptions import SSLError
|
||||||
from twisted.internet import reactor
|
from twisted.internet import reactor, defer
|
||||||
from twisted.internet import task
|
from twisted.internet import task
|
||||||
from typing import Dict, ClassVar, Set
|
from twisted.internet.threads import deferToThread
|
||||||
from typing import Tuple
|
|
||||||
from typing import Union, List
|
|
||||||
from umbral.keys import UmbralPublicKey
|
from umbral.keys import UmbralPublicKey
|
||||||
from umbral.signing import Signature
|
from umbral.signing import Signature
|
||||||
|
|
||||||
|
@ -29,7 +30,6 @@ from nucypher.crypto.powers import CryptoPower, SigningPower, EncryptingPower, N
|
||||||
from nucypher.crypto.signing import signature_splitter, StrangerStamp, SignatureStamp
|
from nucypher.crypto.signing import signature_splitter, StrangerStamp, SignatureStamp
|
||||||
from nucypher.network.middleware import RestMiddleware
|
from nucypher.network.middleware import RestMiddleware
|
||||||
from nucypher.network.nodes import VerifiableNode
|
from nucypher.network.nodes import VerifiableNode
|
||||||
from nucypher.network.server import TLSHostingPower
|
|
||||||
|
|
||||||
|
|
||||||
class Learner:
|
class Learner:
|
||||||
|
@ -63,12 +63,12 @@ class Learner:
|
||||||
known_nodes: tuple = None,
|
known_nodes: tuple = None,
|
||||||
seed_nodes: Tuple[tuple] = None,
|
seed_nodes: Tuple[tuple] = None,
|
||||||
known_certificates_dir: str = None,
|
known_certificates_dir: str = None,
|
||||||
node_storage = None,
|
node_storage=None,
|
||||||
save_metadata: bool = False,
|
save_metadata: bool = False,
|
||||||
abort_on_learning_error: bool = False
|
abort_on_learning_error: bool = False
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|
||||||
self.log = getLogger("characters") # type: Logger
|
self.log = getLogger("characters") # type: Logger
|
||||||
|
|
||||||
self.__common_name = common_name
|
self.__common_name = common_name
|
||||||
self.network_middleware = network_middleware
|
self.network_middleware = network_middleware
|
||||||
|
@ -85,7 +85,8 @@ class Learner:
|
||||||
|
|
||||||
# Read
|
# Read
|
||||||
if node_storage is None:
|
if node_storage is None:
|
||||||
node_storage = self.__DEFAULT_NODE_STORAGE(federated_only=self.federated_only, #TODO: remove federated_only
|
node_storage = self.__DEFAULT_NODE_STORAGE(federated_only=self.federated_only,
|
||||||
|
# TODO: remove federated_only
|
||||||
character_class=self.__class__)
|
character_class=self.__class__)
|
||||||
|
|
||||||
self.node_storage = node_storage
|
self.node_storage = node_storage
|
||||||
|
@ -101,9 +102,9 @@ class Learner:
|
||||||
self.unresponsive_startup_nodes.append(node)
|
self.unresponsive_startup_nodes.append(node)
|
||||||
|
|
||||||
self.teacher_nodes = deque()
|
self.teacher_nodes = deque()
|
||||||
self._current_teacher_node = None # type: Teacher
|
self._current_teacher_node = None # type: Teacher
|
||||||
self._learning_task = task.LoopingCall(self.keep_learning_about_nodes)
|
self._learning_task = task.LoopingCall(self.keep_learning_about_nodes)
|
||||||
self._learning_round = 0 # type: int
|
self._learning_round = 0 # type: int
|
||||||
self._rounds_without_new_nodes = 0 # type: int
|
self._rounds_without_new_nodes = 0 # type: int
|
||||||
self._seed_nodes = seed_nodes or []
|
self._seed_nodes = seed_nodes or []
|
||||||
|
|
||||||
|
@ -120,40 +121,38 @@ class Learner:
|
||||||
retry_rate: int = 2,
|
retry_rate: int = 2,
|
||||||
timeout=3):
|
timeout=3):
|
||||||
"""
|
"""
|
||||||
Engage known nodes from storages and pre-fetch hardcoded bootnode certificates for node learning.
|
Engage known nodes from storages and pre-fetch hardcoded seednode certificates for node learning.
|
||||||
"""
|
"""
|
||||||
def __attempt_bootnode_learning(bootnode, current_attempt=1):
|
|
||||||
self.log.debug("Loading Bootnode {}|{}:{}".format(bootnode.checksum_address, bootnode.rest_host, bootnode.rest_port))
|
|
||||||
|
|
||||||
try:
|
|
||||||
seed_node = self.network_middleware.learn_from_seednode(seednode_metadata=bootnode,
|
|
||||||
timeout=timeout,
|
|
||||||
accept_federated_only=self.federated_only) # TODO: 466
|
|
||||||
self.remember_node(seed_node)
|
|
||||||
except RuntimeError:
|
|
||||||
if current_attempt == retry_attempts:
|
|
||||||
message = "No Response from Bootnode {} after {} attempts"
|
|
||||||
self.log.info(message.format(bootnode.rest_url, retry_attempts))
|
|
||||||
return
|
|
||||||
unresponsive_seed_nodes.add(bootnode)
|
|
||||||
self.log.info("No Response from Bootnode {}. Retrying in {} seconds...".format(bootnode.rest_url, retry_rate))
|
|
||||||
time.sleep(retry_rate)
|
|
||||||
__attempt_bootnode_learning(bootnode=bootnode, current_attempt=current_attempt+1)
|
|
||||||
else:
|
|
||||||
self.log.info("Successfully learned from {}|{}:{}".format(bootnode.checksum_address, bootnode.rest_host, bootnode.rest_port))
|
|
||||||
if current_attempt > 1:
|
|
||||||
unresponsive_seed_nodes.remove(bootnode)
|
|
||||||
|
|
||||||
for bootnode in self._seed_nodes:
|
|
||||||
__attempt_bootnode_learning(bootnode=bootnode)
|
|
||||||
|
|
||||||
unresponsive_seed_nodes = set()
|
unresponsive_seed_nodes = set()
|
||||||
|
|
||||||
if len(unresponsive_seed_nodes) > 0:
|
def __attempt_seednode_learning(seednode_metadata, current_attempt=1):
|
||||||
self.log.info("No Bootnodes were availible after {} attempts".format(retry_attempts))
|
self.log.debug(
|
||||||
|
"Seeding from: {}|{}:{}".format(seednode_metadata.checksum_address,
|
||||||
|
seednode_metadata.rest_host,
|
||||||
|
seednode_metadata.rest_port))
|
||||||
|
seed_node = self.network_middleware.learn_about_seednode(seednode_metadata=seednode_metadata,
|
||||||
|
known_certs_dir=self.known_certificates_dir,
|
||||||
|
timeout=timeout,
|
||||||
|
accept_federated_only=self.federated_only) # TODO: 466
|
||||||
|
if seed_node is False:
|
||||||
|
unresponsive_seed_nodes.add(seednode_metadata)
|
||||||
|
else:
|
||||||
|
self.remember_node(seed_node)
|
||||||
|
|
||||||
# if read_storages is True:
|
for seednode_metadata in self._seed_nodes:
|
||||||
# self.read_known_nodes()
|
__attempt_seednode_learning(seednode_metadata=seednode_metadata)
|
||||||
|
|
||||||
|
if read_storages is True:
|
||||||
|
self.read_nodes_from_storage()
|
||||||
|
|
||||||
|
if not self.known_nodes:
|
||||||
|
self.log.warning("No seednodes were available after {} attempts".format(retry_attempts))
|
||||||
|
# TODO: Need some actual logic here for situation with no seed nodes (ie, maybe try again much later)
|
||||||
|
|
||||||
|
def read_nodes_from_storage(self) -> set:
|
||||||
|
stored_nodes = self.node_storage.all(federated_only=self.federated_only) # TODO: 466
|
||||||
|
for node in stored_nodes:
|
||||||
|
self.remember_node(node)
|
||||||
|
|
||||||
def remember_node(self, node, force_verification_check=False):
|
def remember_node(self, node, force_verification_check=False):
|
||||||
|
|
||||||
|
@ -194,11 +193,17 @@ class Learner:
|
||||||
def start_learning_loop(self, now=False):
|
def start_learning_loop(self, now=False):
|
||||||
if self._learning_task.running:
|
if self._learning_task.running:
|
||||||
return False
|
return False
|
||||||
else:
|
elif now:
|
||||||
self.load_seednodes()
|
self.load_seednodes()
|
||||||
d = self._learning_task.start(interval=self._SHORT_LEARNING_DELAY, now=now)
|
d = self._learning_task.start(interval=self._SHORT_LEARNING_DELAY, now=now)
|
||||||
d.addErrback(self.handle_learning_errors)
|
d.addErrback(self.handle_learning_errors)
|
||||||
return d
|
return d
|
||||||
|
else:
|
||||||
|
seeder_deferred = deferToThread(self.load_seednodes)
|
||||||
|
learner_deferred = self._learning_task.start(interval=self._SHORT_LEARNING_DELAY, now=now)
|
||||||
|
seeder_deferred.addErrback(self.handle_learning_errors)
|
||||||
|
learner_deferred.addErrback(self.handle_learning_errors)
|
||||||
|
return defer.DeferredList([seeder_deferred, learner_deferred])
|
||||||
|
|
||||||
def handle_learning_errors(self, *args, **kwargs):
|
def handle_learning_errors(self, *args, **kwargs):
|
||||||
failure = args[0]
|
failure = args[0]
|
||||||
|
@ -334,8 +339,9 @@ class Learner:
|
||||||
elif not self._learning_task.running:
|
elif not self._learning_task.running:
|
||||||
raise self.NotEnoughTeachers("The learning loop is not running. Start it with start_learning().")
|
raise self.NotEnoughTeachers("The learning loop is not running. Start it with start_learning().")
|
||||||
else:
|
else:
|
||||||
raise self.NotEnoughTeachers("After {} seconds and {} rounds, didn't find these {} nodes: {}".format(
|
raise self.NotEnoughTeachers(
|
||||||
timeout, rounds_undertaken, len(still_unknown), still_unknown))
|
"After {} seconds and {} rounds, didn't find these {} nodes: {}".format(
|
||||||
|
timeout, rounds_undertaken, len(still_unknown), still_unknown))
|
||||||
|
|
||||||
else:
|
else:
|
||||||
time.sleep(.1)
|
time.sleep(.1)
|
||||||
|
@ -411,7 +417,8 @@ class Learner:
|
||||||
try:
|
try:
|
||||||
|
|
||||||
# TODO: Streamline path generation
|
# TODO: Streamline path generation
|
||||||
certificate_filepath = current_teacher.get_certificate_filepath(certificates_dir=self.known_certificates_dir)
|
certificate_filepath = current_teacher.get_certificate_filepath(
|
||||||
|
certificates_dir=self.known_certificates_dir)
|
||||||
response = self.network_middleware.get_nodes_via_rest(url=rest_url,
|
response = self.network_middleware.get_nodes_via_rest(url=rest_url,
|
||||||
nodes_i_need=self._node_ids_to_learn_about_immediately,
|
nodes_i_need=self._node_ids_to_learn_about_immediately,
|
||||||
announce_nodes=announce_nodes,
|
announce_nodes=announce_nodes,
|
||||||
|
@ -442,7 +449,8 @@ class Learner:
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if eager:
|
if eager:
|
||||||
certificate_filepath = current_teacher.get_certificate_filepath(certificates_dir=certificate_filepath)
|
certificate_filepath = current_teacher.get_certificate_filepath(
|
||||||
|
certificates_dir=certificate_filepath)
|
||||||
node.verify_node(self.network_middleware,
|
node.verify_node(self.network_middleware,
|
||||||
accept_federated_only=self.federated_only, # TODO: 466
|
accept_federated_only=self.federated_only, # TODO: 466
|
||||||
certificate_filepath=certificate_filepath)
|
certificate_filepath=certificate_filepath)
|
||||||
|
@ -528,7 +536,7 @@ class Character(Learner):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.federated_only = federated_only # type: bool
|
self.federated_only = federated_only # type: bool
|
||||||
|
|
||||||
#
|
#
|
||||||
# Powers
|
# Powers
|
||||||
|
@ -538,7 +546,7 @@ class Character(Learner):
|
||||||
crypto_power_ups = crypto_power_ups or list() # type: list
|
crypto_power_ups = crypto_power_ups or list() # type: list
|
||||||
|
|
||||||
if crypto_power:
|
if crypto_power:
|
||||||
self._crypto_power = crypto_power # type: CryptoPower
|
self._crypto_power = crypto_power # type: CryptoPower
|
||||||
elif crypto_power_ups:
|
elif crypto_power_ups:
|
||||||
self._crypto_power = CryptoPower(power_ups=crypto_power_ups)
|
self._crypto_power = CryptoPower(power_ups=crypto_power_ups)
|
||||||
else:
|
else:
|
||||||
|
@ -552,7 +560,7 @@ class Character(Learner):
|
||||||
self.blockchain = blockchain or Blockchain.connect()
|
self.blockchain = blockchain or Blockchain.connect()
|
||||||
|
|
||||||
self.keyring_dir = keyring_dir # type: str
|
self.keyring_dir = keyring_dir # type: str
|
||||||
self.treasure_maps = {} # type: dict
|
self.treasure_maps = {} # type: dict
|
||||||
self.network_middleware = network_middleware or RestMiddleware()
|
self.network_middleware = network_middleware or RestMiddleware()
|
||||||
|
|
||||||
#
|
#
|
||||||
|
@ -560,7 +568,7 @@ class Character(Learner):
|
||||||
#
|
#
|
||||||
try:
|
try:
|
||||||
signing_power = self._crypto_power.power_ups(SigningPower) # type: SigningPower
|
signing_power = self._crypto_power.power_ups(SigningPower) # type: SigningPower
|
||||||
self._stamp = signing_power.get_signature_stamp() # type: SignatureStamp
|
self._stamp = signing_power.get_signature_stamp() # type: SignatureStamp
|
||||||
except NoSigningPower:
|
except NoSigningPower:
|
||||||
self._stamp = constants.NO_SIGNING_POWER
|
self._stamp = constants.NO_SIGNING_POWER
|
||||||
|
|
||||||
|
|
|
@ -355,11 +355,6 @@ class NodeConfiguration:
|
||||||
self.validate(config_root=self.config_root, no_registry=no_registry or self.federated_only)
|
self.validate(config_root=self.config_root, no_registry=no_registry or self.federated_only)
|
||||||
return self.config_root
|
return self.config_root
|
||||||
|
|
||||||
def read_known_nodes(self) -> set:
|
|
||||||
"""Read known nodes from metadata, and use them when producing a character"""
|
|
||||||
known_nodes = self.node_storage.all(federated_only=self.federated_only)
|
|
||||||
return known_nodes
|
|
||||||
|
|
||||||
def read_keyring(self, *args, **kwargs):
|
def read_keyring(self, *args, **kwargs):
|
||||||
if self.checksum_address is None:
|
if self.checksum_address is None:
|
||||||
raise self.ConfigurationError("No account specified to unlock keyring")
|
raise self.ConfigurationError("No account specified to unlock keyring")
|
||||||
|
|
|
@ -1,9 +1,17 @@
|
||||||
|
import os
|
||||||
|
import socket
|
||||||
|
import ssl
|
||||||
|
import time
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
from bytestring_splitter import BytestringSplitter, VariableLengthBytestring
|
from bytestring_splitter import BytestringSplitter, VariableLengthBytestring
|
||||||
|
from cryptography import x509
|
||||||
from umbral.fragments import CapsuleFrag
|
from cryptography.hazmat.backends import default_backend
|
||||||
|
from cryptography.hazmat.primitives.serialization import Encoding
|
||||||
from twisted.logger import Logger
|
from twisted.logger import Logger
|
||||||
|
from umbral.fragments import CapsuleFrag
|
||||||
|
|
||||||
|
from nucypher.config.keyring import _write_tls_certificate
|
||||||
|
|
||||||
|
|
||||||
class RestMiddleware:
|
class RestMiddleware:
|
||||||
|
@ -19,16 +27,64 @@ class RestMiddleware:
|
||||||
raise RuntimeError("Bad response: {}".format(response.content))
|
raise RuntimeError("Bad response: {}".format(response.content))
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def _get_certificate(self, hostname, port):
|
def learn_about_seednode(self, seednode_metadata, known_certs_dir, timeout=3, accept_federated_only=False):
|
||||||
bootnode_certificate = ssl.get_server_certificate(hostname, port)
|
from nucypher.characters.lawful import Ursula
|
||||||
certificate = x509.load_pem_x509_certificate(bootnode_certificate.encode(),
|
# Pre-fetch certificate
|
||||||
|
self.log.info("Fetching seednode {} TLS certificate".format(seednode_metadata.checksum_address))
|
||||||
|
|
||||||
|
# TODO: Utilize timeout.
|
||||||
|
certificate, filepath = self._get_certificate(checksum_address=seednode_metadata.checksum_address,
|
||||||
|
hostname=seednode_metadata.rest_host,
|
||||||
|
port=seednode_metadata.rest_port,
|
||||||
|
certs_dir=known_certs_dir,
|
||||||
|
timeout=timeout)
|
||||||
|
|
||||||
|
if certificate is False:
|
||||||
|
return False
|
||||||
|
|
||||||
|
potential_seed_node = Ursula.from_rest_url(self,
|
||||||
|
seednode_metadata.rest_host,
|
||||||
|
seednode_metadata.rest_port,
|
||||||
|
certificate_filepath=filepath,
|
||||||
|
federated_only=True) # TODO: 466
|
||||||
|
|
||||||
|
if not seednode_metadata.checksum_address == potential_seed_node.checksum_public_address:
|
||||||
|
raise potential_seed_node.SuspiciousActivity(
|
||||||
|
"This seed node has a different wallet address: {} (was hoping for {}). Are you sure this is a seed node?".format(
|
||||||
|
potential_seed_node.checksum_public_address,
|
||||||
|
seednode_metadata.checksum_address))
|
||||||
|
try:
|
||||||
|
potential_seed_node.verify_node(self,
|
||||||
|
accept_federated_only=accept_federated_only,
|
||||||
|
certificate_filepath=filepath)
|
||||||
|
except potential_seed_node.InvalidNode:
|
||||||
|
raise # TODO: What if our seed node fails verification?
|
||||||
|
|
||||||
|
return potential_seed_node
|
||||||
|
|
||||||
|
def _get_certificate(self, checksum_address, certs_dir, hostname, port,
|
||||||
|
timeout=3, retry_attempts: int=3, retry_rate: int = 2,):
|
||||||
|
socket.setdefaulttimeout(timeout) # Set Socket Timeout
|
||||||
|
current_attempt = 0
|
||||||
|
try:
|
||||||
|
seednode_certificate = ssl.get_server_certificate(addr=(hostname, port))
|
||||||
|
except socket.timeout:
|
||||||
|
if current_attempt == retry_attempts:
|
||||||
|
message = "No Response from seednode {} after {} attempts"
|
||||||
|
self.log.info(message.format(checksum_address, retry_attempts))
|
||||||
|
return False, False
|
||||||
|
self.log.info(
|
||||||
|
"No Response from seednode {}. Retrying in {} seconds...".format(checksum_address, retry_rate))
|
||||||
|
time.sleep(retry_rate)
|
||||||
|
|
||||||
|
certificate = x509.load_pem_x509_certificate(seednode_certificate.encode(),
|
||||||
backend=default_backend())
|
backend=default_backend())
|
||||||
# Write certificate
|
# Write certificate
|
||||||
filename = '{}.{}'.format(bootnode.checksum_address, Encoding.PEM.name.lower())
|
filename = '{}.{}'.format(checksum_address, Encoding.PEM.name.lower())
|
||||||
certificate_filepath = os.path.join(self.known_certificates_dir, filename)
|
certificate_filepath = os.path.join(certs_dir, filename)
|
||||||
_write_tls_certificate(certificate=certificate, full_filepath=certificate_filepath, force=True)
|
_write_tls_certificate(certificate=certificate, full_filepath=certificate_filepath, force=True)
|
||||||
self.log.info("Saved bootnode {} TLS certificate".format(bootnode.checksum_address))
|
self.log.info("Saved seednode {} TLS certificate".format(checksum_address))
|
||||||
|
return certificate, certificate_filepath
|
||||||
|
|
||||||
def enact_policy(self, ursula, id, payload):
|
def enact_policy(self, ursula, id, payload):
|
||||||
response = requests.post('https://{}/kFrag/{}'.format(ursula.rest_interface, id.hex()), payload,
|
response = requests.post('https://{}/kFrag/{}'.format(ursula.rest_interface, id.hex()), payload,
|
||||||
|
|
|
@ -1,9 +1,4 @@
|
||||||
from urllib.parse import urlparse
|
|
||||||
|
|
||||||
from apistar import TestClient
|
from apistar import TestClient
|
||||||
from cryptography import x509
|
|
||||||
from cryptography.hazmat.backends import default_backend
|
|
||||||
from cryptography.hazmat.primitives.serialization import Encoding
|
|
||||||
|
|
||||||
from nucypher.characters.lawful import Ursula
|
from nucypher.characters.lawful import Ursula
|
||||||
from nucypher.network.middleware import RestMiddleware
|
from nucypher.network.middleware import RestMiddleware
|
||||||
|
@ -37,31 +32,8 @@ class MockRestMiddleware(RestMiddleware):
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
"Can't find an Ursula with port {} - did you spin up the right test ursulas?".format(port))
|
"Can't find an Ursula with port {} - did you spin up the right test ursulas?".format(port))
|
||||||
|
|
||||||
def learn_from_seednode(self, seednode_metadata, timeout=3, accept_federated_only=False):
|
def _get_certificate(self, checksum_address, certs_dir, hostname, port, timeout=3, retry_attempts: int = 3,
|
||||||
# Pre-fetch certificate
|
retry_rate: int = 2, ):
|
||||||
self.log.info("Fetching bootnode {} TLS certificate".format(seednode_metadata.checksum_address))
|
|
||||||
|
|
||||||
certificate, filepath = self._get_certificate(seednode_metadata.rest_host, seednode_metadata.rest_port)
|
|
||||||
|
|
||||||
potential_seed_node = Ursula.from_rest_url(self,
|
|
||||||
seednode_metadata.rest_host,
|
|
||||||
seednode_metadata.rest_port,
|
|
||||||
certificate_filepath=filepath,
|
|
||||||
federated_only=True) # TODO: 466
|
|
||||||
|
|
||||||
if not seednode_metadata.checksum_address == potential_seed_node.checksum_public_address:
|
|
||||||
raise potential_seed_node.SuspiciousActivity(
|
|
||||||
"This seed node has a different wallet address: {} (was hoping for {}). Are you sure this is a seed node?".format(
|
|
||||||
potential_seed_node.checksum_public_address,
|
|
||||||
bootnode.checksum_address))
|
|
||||||
try:
|
|
||||||
potential_seed_node.verify_node(self, accept_federated_only=accept_federated_only)
|
|
||||||
except potential_seed_node.InvalidNode:
|
|
||||||
raise # TODO: What if our seed node fails verification?
|
|
||||||
|
|
||||||
return potential_seed_node
|
|
||||||
|
|
||||||
def _get_certificate(self, hostname, port):
|
|
||||||
ursula = self._get_ursula_by_port(port)
|
ursula = self._get_ursula_by_port(port)
|
||||||
return ursula.certificate, ursula.certificate_filepath
|
return ursula.certificate, ursula.certificate_filepath
|
||||||
|
|
||||||
|
|
|
@ -143,21 +143,21 @@ class UrsulaCommandProtocol(LineReceiver):
|
||||||
color_index = {
|
color_index = {
|
||||||
'self': 'yellow',
|
'self': 'yellow',
|
||||||
'known': 'white',
|
'known': 'white',
|
||||||
'bootnode': 'blue'
|
'seednode': 'blue'
|
||||||
}
|
}
|
||||||
for node_type, color in color_index.items():
|
for node_type, color in color_index.items():
|
||||||
click.secho('{0:<6} | '.format(node_type), fg=color, nl=False)
|
click.secho('{0:<6} | '.format(node_type), fg=color, nl=False)
|
||||||
click.echo('\n')
|
click.echo('\n')
|
||||||
|
|
||||||
bootnode_addresses = list(bn.checksum_address for bn in BOOTNODES)
|
seednode_addresses = list(bn.checksum_address for bn in BOOTNODES)
|
||||||
for address, node in known_nodes.items():
|
for address, node in known_nodes.items():
|
||||||
row_template = "{} | {} | {}"
|
row_template = "{} | {} | {}"
|
||||||
node_type = 'known'
|
node_type = 'known'
|
||||||
if node.checksum_public_address == self.ursula.checksum_public_address:
|
if node.checksum_public_address == self.ursula.checksum_public_address:
|
||||||
node_type = 'self'
|
node_type = 'self'
|
||||||
row_template += ' ({})'.format(node_type)
|
row_template += ' ({})'.format(node_type)
|
||||||
if node.checksum_public_address in bootnode_addresses:
|
if node.checksum_public_address in seednode_addresses:
|
||||||
node_type = 'bootnode'
|
node_type = 'seednode'
|
||||||
row_template += ' ({})'.format(node_type)
|
row_template += ' ({})'.format(node_type)
|
||||||
click.secho(row_template.format(node.checksum_public_address,
|
click.secho(row_template.format(node.checksum_public_address,
|
||||||
node.rest_url(),
|
node.rest_url(),
|
||||||
|
|
|
@ -1,6 +1,13 @@
|
||||||
from functools import partial
|
from functools import partial
|
||||||
|
|
||||||
|
import maya
|
||||||
|
import pytest
|
||||||
|
import pytest_twisted
|
||||||
|
from twisted.internet.threads import deferToThread
|
||||||
|
|
||||||
|
from nucypher.network.middleware import RestMiddleware
|
||||||
from nucypher.utilities.sandbox.ursula import make_federated_ursulas
|
from nucypher.utilities.sandbox.ursula import make_federated_ursulas
|
||||||
|
from cryptography.hazmat.primitives import serialization
|
||||||
|
|
||||||
|
|
||||||
def test_proper_seed_node_instantiation(ursula_federated_test_config):
|
def test_proper_seed_node_instantiation(ursula_federated_test_config):
|
||||||
|
@ -10,9 +17,44 @@ def test_proper_seed_node_instantiation(ursula_federated_test_config):
|
||||||
know_each_other=False)
|
know_each_other=False)
|
||||||
|
|
||||||
firstula = lonely_ursula_maker().pop()
|
firstula = lonely_ursula_maker().pop()
|
||||||
|
firstula_as_seed_node = firstula.seed_node_metadata()
|
||||||
any_other_ursula = lonely_ursula_maker(seed_nodes=[firstula.seed_node_metadata()]).pop()
|
any_other_ursula = lonely_ursula_maker(seed_nodes=[firstula_as_seed_node]).pop()
|
||||||
|
|
||||||
assert not any_other_ursula.known_nodes
|
assert not any_other_ursula.known_nodes
|
||||||
any_other_ursula.start_learning_loop()
|
any_other_ursula.start_learning_loop(now=True)
|
||||||
assert firstula in any_other_ursula.known_nodes.values()
|
assert firstula in any_other_ursula.known_nodes.values()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest_twisted.inlineCallbacks
|
||||||
|
def test_get_cert_from_running_seed_node(ursula_federated_test_config):
|
||||||
|
lonely_ursula_maker = partial(make_federated_ursulas,
|
||||||
|
ursula_config=ursula_federated_test_config,
|
||||||
|
quantity=1,
|
||||||
|
know_each_other=False)
|
||||||
|
firstula = lonely_ursula_maker().pop()
|
||||||
|
node_deployer = firstula.get_deployer()
|
||||||
|
|
||||||
|
node_deployer.addServices()
|
||||||
|
node_deployer.catalogServers(node_deployer.hendrix)
|
||||||
|
node_deployer.start()
|
||||||
|
|
||||||
|
certificate_as_deployed = node_deployer.cert.to_cryptography()
|
||||||
|
|
||||||
|
firstula_as_seed_node = firstula.seed_node_metadata()
|
||||||
|
any_other_ursula = lonely_ursula_maker(seed_nodes=[firstula_as_seed_node],
|
||||||
|
network_middleware=RestMiddleware()).pop()
|
||||||
|
assert not any_other_ursula.known_nodes
|
||||||
|
|
||||||
|
def start_lonely_learning_loop():
|
||||||
|
any_other_ursula.start_learning_loop()
|
||||||
|
start = maya.now()
|
||||||
|
while not firstula in any_other_ursula.known_nodes.values():
|
||||||
|
passed = maya.now() - start
|
||||||
|
if passed.seconds > 2:
|
||||||
|
pytest.fail("Didn't find the seed node.")
|
||||||
|
|
||||||
|
yield deferToThread(start_lonely_learning_loop)
|
||||||
|
assert firstula in any_other_ursula.known_nodes.values()
|
||||||
|
|
||||||
|
certificate_as_learned = list(any_other_ursula.known_nodes.values())[0].certificate
|
||||||
|
assert certificate_as_learned == certificate_as_deployed
|
||||||
|
|
|
@ -6,7 +6,7 @@ from moto import mock_s3
|
||||||
from nucypher.characters.lawful import Ursula
|
from nucypher.characters.lawful import Ursula
|
||||||
from nucypher.config.storages import S3NodeStorage, InMemoryNodeStorage, TemporaryFileBasedNodeStorage, NodeStorage
|
from nucypher.config.storages import S3NodeStorage, InMemoryNodeStorage, TemporaryFileBasedNodeStorage, NodeStorage
|
||||||
|
|
||||||
MOCK_S3_BUCKET_NAME = 'mock-bootnodes'
|
MOCK_S3_BUCKET_NAME = 'mock-seednodes'
|
||||||
S3_DOMAIN_NAME = 's3.amazonaws.com'
|
S3_DOMAIN_NAME = 's3.amazonaws.com'
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
import os
|
import os
|
||||||
|
|
||||||
import pytest
|
|
||||||
import pytest_twisted
|
import pytest_twisted
|
||||||
import requests
|
import requests
|
||||||
from cryptography.hazmat.primitives import serialization
|
from cryptography.hazmat.primitives import serialization
|
||||||
|
@ -40,13 +38,7 @@ def test_federated_nodes_connect_via_tls_and_verify(ursula_federated_test_config
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with open("test-cert", "wb") as f:
|
with open("test-cert", "wb") as f:
|
||||||
# f.write(cert.tbs_certificate_bytes.hex())
|
|
||||||
f.write(cert_bytes)
|
f.write(cert_bytes)
|
||||||
yield threads.deferToThread(check_node_with_cert, node, "test-cert")
|
yield threads.deferToThread(check_node_with_cert, node, "test-cert")
|
||||||
finally:
|
finally:
|
||||||
os.remove("test-cert")
|
os.remove("test-cert")
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skip(reason="To be implemented")
|
|
||||||
def test_node_metadata_contains_proper_cert():
|
|
||||||
pass
|
|
||||||
|
|
|
@ -1,6 +1,34 @@
|
||||||
|
import maya
|
||||||
import pytest
|
import pytest
|
||||||
|
import pytest_twisted
|
||||||
|
from twisted.internet.threads import deferToThread
|
||||||
|
|
||||||
|
from nucypher.utilities.sandbox.ursula import make_federated_ursulas
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skip("To be implemented.")
|
@pytest_twisted.inlineCallbacks
|
||||||
def test_eager_learn_from_teacher():
|
def test_one_node_stores_a_bunch_of_others(federated_ursulas, ursula_federated_test_config):
|
||||||
assert False
|
the_chosen_seednode = list(federated_ursulas)[2]
|
||||||
|
seed_node = the_chosen_seednode.seed_node_metadata()
|
||||||
|
newcomer = make_federated_ursulas(
|
||||||
|
ursula_config=ursula_federated_test_config,
|
||||||
|
quantity=1,
|
||||||
|
know_each_other=False,
|
||||||
|
save_metadata=True,
|
||||||
|
seed_nodes=[seed_node]).pop()
|
||||||
|
|
||||||
|
assert not newcomer.known_nodes
|
||||||
|
|
||||||
|
def start_lonely_learning_loop():
|
||||||
|
newcomer.start_learning_loop()
|
||||||
|
start = maya.now()
|
||||||
|
# Loop until the_chosen_seednode is in storage.
|
||||||
|
while not the_chosen_seednode in newcomer.node_storage.all(federated_only=True):
|
||||||
|
passed = maya.now() - start
|
||||||
|
if passed.seconds > 2:
|
||||||
|
pytest.fail("Didn't find the seed node.")
|
||||||
|
|
||||||
|
yield deferToThread(start_lonely_learning_loop)
|
||||||
|
|
||||||
|
# The known_nodes are all saved in storage (and no others have been saved)
|
||||||
|
assert list(newcomer.known_nodes.values()) == list(newcomer.node_storage.all(True))
|
||||||
|
|
Loading…
Reference in New Issue