Merge pull request #479 from KPrasch/seeds

Load Hardcoded Bootnodes at Startup
pull/480/head
K Prasch 2018-10-14 20:17:47 -07:00 committed by GitHub
commit 4131c8ca0c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 100 additions and 8 deletions

View File

@ -6,6 +6,7 @@ import logging
import os
import random
import shutil
import ssl
import sys
from typing import Tuple, ClassVar
@ -1168,10 +1169,8 @@ def ursula(config,
config.operating_mode = "federated" if ursula_config.federated_only else "decentralized"
click.secho("Running in {} mode".format(config.operating_mode), fg='blue')
# ursula_config.read_known_nodes(known_metadata_dir=additional_nodes)
# if additional_nodes: # Secondary override
# click.secho("Loaded additional known nodes", color='blue')
# Bootnodes, Seeds, Known Nodes
ursula_config.get_bootnodes()
quantity_known_nodes = len(ursula_config.known_nodes)
if quantity_known_nodes > 0:
click.secho("Loaded {} known nodes from storages".format(quantity_known_nodes, fg='blue'))

View File

@ -1,4 +1,5 @@
import os
from collections import namedtuple
from os.path import abspath, dirname
from appdirs import AppDirs
@ -6,12 +7,17 @@ from appdirs import AppDirs
import nucypher
# Base Filepaths
BASE_DIR = abspath(dirname(dirname(nucypher.__file__)))
PROJECT_ROOT = abspath(dirname(nucypher.__file__))
APP_DIR = AppDirs("nucypher", "NuCypher")
DEFAULT_CONFIG_ROOT = APP_DIR.user_data_dir
# Bootnodes
Bootnode = namedtuple('Bootnode', ['checksum_address', 'rest_url'])
BOOTNODES = (
Bootnode('0xDbf2Bc4b81eB46CdDfa52348Ecf3c142841267E0', 'https://18.223.117.103:9151'),
)
# Test Constants # TODO: Tidy up filepath here
TEST_CONTRACTS_DIR = os.path.join(BASE_DIR, 'tests', 'blockchain', 'eth', 'contracts', 'contracts')
NUMBER_OF_URSULAS_IN_MOCK_NETWORK = 10

View File

@ -1,18 +1,29 @@
import binascii
import json
import os
import socket
import ssl
import time
from json import JSONDecodeError
from logging import getLogger
from tempfile import TemporaryDirectory
from typing import List
from urllib.parse import urlparse
import requests
from constant_sorrow import constants
from cryptography import x509
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.serialization import Encoding
from nucypher.config.constants import DEFAULT_CONFIG_ROOT, BASE_DIR
from nucypher.config.keyring import NucypherKeyring
from nucypher.characters.lawful import Ursula
from nucypher.config.constants import DEFAULT_CONFIG_ROOT, BASE_DIR, BOOTNODES
from nucypher.config.keyring import NucypherKeyring, _write_tls_certificate, _read_tls_public_certificate
from nucypher.config.storages import NodeStorage, InMemoryNodeStorage
from nucypher.crypto.powers import CryptoPowerUp
from nucypher.crypto.signing import signature_splitter
from nucypher.network.middleware import RestMiddleware
from nucypher.network.nodes import VerifiableNode
class NodeConfiguration:
@ -185,8 +196,11 @@ class NodeConfiguration:
storage_type = storage_payload[NodeStorage._TYPE_LABEL]
storage_class = NODE_STORAGES[storage_type]
node_storage = storage_class.from_payload(payload=storage_payload,
character_class=cls._Character,
federated_only=payload['federated_only'],
serializer=cls.NODE_SERIALIZER,
deserializer=cls.NODE_DESERIALIZER)
payload.update(dict(node_storage=node_storage))
return cls(**{**payload, **overrides})
@ -354,7 +368,7 @@ class NodeConfiguration:
def read_known_nodes(self) -> set:
"""Read known nodes from metadata, and use them when producing a character"""
known_nodes = self.node_storage.all()
known_nodes = self.node_storage.all(federated_only=self.federated_only)
return known_nodes
def read_keyring(self, *args, **kwargs):
@ -421,3 +435,76 @@ class NodeConfiguration:
self.log.info("Successfully wrote registry to {}".format(output_filepath))
return output_filepath
def __learn_from_bootnode(self, bootnode):
parsed_url = urlparse(bootnode.rest_url)
# Pre-fetch certificate
self.log.info("Fetching bootnode {} TLS certificate".format(bootnode.checksum_address))
bootnode_certificate = ssl.get_server_certificate((parsed_url.hostname, parsed_url.port))
certificate = x509.load_pem_x509_certificate(bootnode_certificate.encode(),
backend=default_backend())
# Write certificate
filename = '{}.{}'.format(bootnode.checksum_address, Encoding.PEM.name.lower())
certificate_filepath = os.path.join(self.known_certificates_dir, filename)
_write_tls_certificate(certificate=certificate, full_filepath=certificate_filepath, force=True)
self.log.info("Saved bootnode {} TLS certificate".format(bootnode.checksum_address))
# Learn from Bootnode
response = self.network_middleware.get_nodes_via_rest(url=parsed_url.netloc,
certificate_filepath=certificate_filepath)
self.log.info("Retrieved bootnode data from {}".format(bootnode.checksum_address))
if response.status_code != 200:
raise RuntimeError("Bad response from bootnode {}".format(bootnode.rest_url))
signature, nodes = signature_splitter(response.content, return_remainder=True)
node_list = Ursula.batch_from_bytes(nodes, federated_only=self.federated_only) # TODO: 466
self.log.debug("Learned from Bootnode {}|{}".format(bootnode.checksum_address, parsed_url.geturl()))
for node in node_list:
self.known_nodes.add(node)
return node_list
def load_bootnodes(self,
read_storages: bool = True,
load_seed_nodes: bool = True,
retry_attempts: int = 3,
retry_rate: int = 2,
timeout=3):
"""
Engage known nodes from storages and pre-fetch hardcoded bootnode certificates for node learning.
"""
if load_seed_nodes is True:
socket.setdefaulttimeout(timeout) # Set Socket Timeout
unresponsive_seed_nodes = set()
def __attempt_bootnode_learning(bootnode, current_attempt=1):
self.log.debug("Loading Bootnode {}|{}".format(bootnode.checksum_address, bootnode.rest_url))
try:
self.__learn_from_bootnode(bootnode=bootnode)
except socket.timeout:
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 bootnode {}".format(bootnode.rest_url))
if current_attempt > 1:
unresponsive_seed_nodes.remove(bootnode)
for bootnode in BOOTNODES:
__attempt_bootnode_learning(bootnode=bootnode)
if len(unresponsive_seed_nodes) > 0:
self.log.info("No Bootnodes were availible after {} attempts".format(retry_attempts))
if read_storages is True:
self.read_known_nodes()