Ensure blockchain is injected into decentralized Characters, Addresses #1202

pull/1242/head
Kieran Prasch 2019-08-07 10:56:11 -07:00 committed by David Núñez
parent 4e3395726a
commit f8db89d1a8
5 changed files with 86 additions and 57 deletions

View File

@ -106,6 +106,12 @@ class Character(Learner):
represented by zero Characters or by more than one Character.
"""
#
# Operating Mode
#
if federated_only is False and BlockchainInterface is None:
raise ValueError("No blockchain interface provided to initialize decentralized Character.")
self.federated_only = federated_only # type: bool
#

View File

@ -45,6 +45,7 @@ import nucypher
from nucypher.blockchain.eth.actors import PolicyAuthor, Worker
from nucypher.blockchain.eth.agents import StakingEscrowAgent
from nucypher.blockchain.eth.decorators import validate_checksum_address
from nucypher.blockchain.eth.interfaces import BlockchainInterface
from nucypher.blockchain.eth.token import StakeTracker
from nucypher.blockchain.eth.utils import calculate_period_duration, datetime_at_period
from nucypher.characters.banners import ALICE_BANNER, BOB_BANNER, ENRICO_BANNER, URSULA_BANNER
@ -838,6 +839,7 @@ class Ursula(Teacher, Character, Worker):
timestamp=None,
# Blockchain
blockchain: BlockchainInterface = None,
decentralized_identity_evidence: bytes = constants.NOT_SIGNED,
checksum_address: str = None, # Staker address
worker_address: str = None,
@ -845,7 +847,6 @@ class Ursula(Teacher, Character, Worker):
client_password: str = None,
# Character
password: str = None,
abort_on_learning_error: bool = False,
federated_only: bool = False,
start_learning_now: bool = None,
@ -875,6 +876,7 @@ class Ursula(Teacher, Character, Worker):
abort_on_learning_error=abort_on_learning_error,
known_nodes=known_nodes,
domains=domains,
blockchain=blockchain,
**character_kwargs)
#
@ -1076,10 +1078,9 @@ class Ursula(Teacher, Character, Worker):
teacher_uri: str,
min_stake: int,
network_middleware: RestMiddleware = None,
blockchain=None,
) -> 'Ursula':
hostname, port, checksum_address = parse_node_uri(uri=teacher_uri)
def __attempt(attempt=1, interval=10) -> Ursula:
if attempt > 3:
raise ConnectionRefusedError("Host {} Refused Connection".format(teacher_uri))
@ -1089,7 +1090,8 @@ class Ursula(Teacher, Character, Worker):
federated_only=federated_only,
checksum_address=checksum_address,
minimum_stake=min_stake,
network_middleware=network_middleware)
network_middleware=network_middleware,
blockchain=blockchain)
except NodeSeemsToBeDown:
log = Logger(cls.__name__)
@ -1107,66 +1109,61 @@ class Ursula(Teacher, Character, Worker):
seed_uri: str,
federated_only: bool,
minimum_stake: int = 0,
checksum_address: str = None, # TODO: Why is this unused?
blockchain: BlockchainInterface = None,
network_middleware: RestMiddleware = None,
*args,
**kwargs
) -> 'Ursula':
if network_middleware is None:
network_middleware = RestMiddleware()
#
# WARNING: xxx Poison xxx
# Let's learn what we can about the ... "seednode".
#
if network_middleware is None:
network_middleware = RestMiddleware()
# Parse node URI
host, port, checksum_address = parse_node_uri(seed_uri)
# 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
# Create a temporary certificate storage area
temp_node_storage = ForgetfulNodeStorage(federated_only=federated_only)
certificate_filepath = temp_node_storage.store_node_certificate(certificate=certificate)
temp_certificate_filepath = temp_node_storage.store_node_certificate(certificate=certificate)
# Load the host as a potential seed node
potential_seed_node = cls.from_rest_url(
blockchain=blockchain,
host=real_host,
port=port,
network_middleware=network_middleware,
certificate_filepath=certificate_filepath,
certificate_filepath=temp_certificate_filepath,
federated_only=federated_only,
*args,
**kwargs) # TODO: 466
potential_seed_node.certificate_filepath = certificate_filepath
if checksum_address:
# Ensure this is the specific node we expected
if not checksum_address == potential_seed_node.checksum_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_address,
checksum_address))
**kwargs
)
# 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.")
if minimum_stake > 0 and not federated_only:
staking_agent = StakingEscrowAgent(blockchain=blockchain)
seednode_stake = staking_agent.get_locked_tokens(staker_address=checksum_address)
if seednode_stake < minimum_stake:
raise Learner.NotATeacher(f"{checksum_address} is staking less then the specified minimum stake value ({minimum_stake}).")
# 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)
potential_seed_node.verify_node(network_middleware=network_middleware,
accept_federated_only=federated_only,
certificate_filepath=temp_certificate_filepath)
except potential_seed_node.InvalidNode:
raise # TODO: What if our seed node fails verification?
# TODO: What if our seed node fails verification?
raise
# OK - everyone get out
temp_node_storage.forget()
return potential_seed_node
@classmethod
@ -1190,6 +1187,7 @@ class Ursula(Teacher, Character, Worker):
ursula_as_bytes: bytes,
version: int = INCLUDED_IN_BYTESTRING,
federated_only: bool = False,
blockchain: BlockchainInterface = None,
) -> 'Ursula':
if version is INCLUDED_IN_BYTESTRING:
@ -1226,7 +1224,7 @@ class Ursula(Teacher, Character, Worker):
domains_vbytes = VariableLengthBytestring.dispense(node_info['domains'])
node_info['domains'] = set(d.decode('utf-8') for d in domains_vbytes)
ursula = cls.from_public_keys(federated_only=federated_only, **node_info)
ursula = cls.from_public_keys(blockchain=blockchain, federated_only=federated_only, **node_info)
return ursula
@classmethod

View File

@ -104,7 +104,8 @@ def load_seednodes(emitter,
federated_only: bool,
network_domains: set,
network_middleware: RestMiddleware = None,
teacher_uris: list = None
teacher_uris: list = None,
blockchain=None,
) -> List[Ursula]:
# Set domains
@ -118,17 +119,22 @@ def load_seednodes(emitter,
for domain in network_domains:
try:
teacher_uris = TEACHER_NODES[domain]
# Known NuCypher Domain
seednode_uris = TEACHER_NODES[domain]
except KeyError:
# TODO: If this is a unknown domain, require the caller to pass a teacher URI explicitly?
# Unknown NuCypher Domain
if not teacher_uris:
emitter.message(f"No default teacher nodes exist for the specified network: {domain}")
else:
# Prefer the injected teacher URI, then use the hardcoded seednodes.
teacher_uris.append(seednode_uris)
for uri in teacher_uris:
teacher_node = Ursula.from_teacher_uri(teacher_uri=uri,
min_stake=min_stake,
federated_only=federated_only,
network_middleware=network_middleware)
network_middleware=network_middleware,
blockchain=blockchain)
teacher_nodes.append(teacher_node)
if not teacher_nodes:
@ -250,6 +256,7 @@ def make_cli_character(character_config,
# Handle Blockchain
if not character_config.federated_only:
character_config.get_blockchain_interface()
character_config.blockchain.connect(fetch_registry=False)
# Handle Keyring
@ -260,14 +267,13 @@ def make_cli_character(character_config,
password=get_nucypher_password(confirm=False))
# Handle Teachers
teacher_nodes = None
if teacher_uri:
teacher_nodes = load_seednodes(emitter,
teacher_uris=[teacher_uri] if teacher_uri else None,
min_stake=min_stake,
federated_only=character_config.federated_only,
network_domains=character_config.domains,
network_middleware=character_config.network_middleware)
teacher_nodes = load_seednodes(emitter,
teacher_uris=[teacher_uri] if teacher_uri else None,
min_stake=min_stake,
federated_only=character_config.federated_only,
network_domains=character_config.domains,
network_middleware=character_config.network_middleware,
blockchain=character_config.blockchain)
#
# Character Init

View File

@ -219,9 +219,12 @@ class CharacterConfiguration(BaseConfiguration):
def __setup_node_storage(self, node_storage=None) -> None:
if self.dev_mode:
node_storage = ForgetfulNodeStorage(federated_only=self.federated_only)
node_storage = ForgetfulNodeStorage(blockchain=self.blockchain,
federated_only=self.federated_only)
elif not node_storage:
node_storage = LocalFileBasedNodeStorage(federated_only=self.federated_only, config_root=self.config_root)
node_storage = LocalFileBasedNodeStorage(blockchain=self.blockchain,
federated_only=self.federated_only,
config_root=self.config_root)
self.node_storage = node_storage
def read_known_nodes(self, additional_nodes=None) -> None:
@ -333,15 +336,17 @@ class CharacterConfiguration(BaseConfiguration):
@property
def dynamic_payload(self) -> dict:
"""Exported dynamic configuration values for initializing Ursula"""
self.read_known_nodes()
payload = dict(network_middleware=self.network_middleware or self.DEFAULT_NETWORK_MIDDLEWARE(),
known_nodes=self.known_nodes,
node_storage=self.node_storage,
crypto_power_ups=self.derive_node_power_ups())
payload = dict()
if not self.federated_only:
self.get_blockchain_interface()
self.blockchain.connect() # TODO: This makes blockchain connection more eager than transacting power acivation
self.blockchain.connect()
payload.update(blockchain=self.blockchain)
self.read_known_nodes() # Requires a connected blockchain
payload.update(dict(network_middleware=self.network_middleware or self.DEFAULT_NETWORK_MIDDLEWARE(),
known_nodes=self.known_nodes,
node_storage=self.node_storage,
crypto_power_ups=self.derive_node_power_ups()))
return payload
def generate_filepath(self, filepath: str = None, modifier: str = None, override: bool = False) -> str:

View File

@ -30,6 +30,7 @@ from eth_utils import is_checksum_address
from twisted.logger import Logger
from typing import Callable, Tuple, Union, Set, Any
from nucypher.blockchain.eth.interfaces import BlockchainInterface
from nucypher.config.constants import DEFAULT_CONFIG_ROOT
from nucypher.blockchain.eth.decorators import validate_checksum_address
@ -53,11 +54,14 @@ class NodeStorage(ABC):
character_class=None,
serializer: Callable = NODE_SERIALIZER,
deserializer: Callable = NODE_DESERIALIZER,
blockchain: BlockchainInterface = None
) -> None:
# TODO: Use blockchain None to indicate federated status.
from nucypher.characters.lawful import Ursula
self.log = Logger(self.__class__.__name__)
self.blockchain = blockchain
self.serializer = serializer
self.deserializer = deserializer
self.federated_only = federated_only
@ -365,13 +369,19 @@ class LocalFileBasedNodeStorage(NodeStorage):
self.__METADATA_FILENAME_TEMPLATE.format(checksum_address))
return metadata_path
def __read_metadata(self, filepath: str, federated_only: bool):
def __read_metadata(self,
filepath: str,
federated_only: bool,
blockchain: BlockchainInterface = None):
# TODO: Use blockchain None to indicate federated only
from nucypher.characters.lawful import Ursula
try:
with open(filepath, "rb") as seed_file:
seed_file.seek(0)
node_bytes = self.deserializer(seed_file.read())
node = Ursula.from_bytes(node_bytes, federated_only=federated_only)
node = Ursula.from_bytes(node_bytes, blockchain=blockchain, federated_only=federated_only)
except FileNotFoundError:
raise self.UnknownNode
return node
@ -400,7 +410,9 @@ class LocalFileBasedNodeStorage(NodeStorage):
known_nodes = set()
for filename in filenames:
metadata_path = os.path.join(self.metadata_dir, filename)
node = self.__read_metadata(filepath=metadata_path, federated_only=federated_only) # TODO: 466
node = self.__read_metadata(filepath=metadata_path,
blockchain=self.blockchain,
federated_only=federated_only) # TODO: 466
known_nodes.add(node)
return known_nodes
@ -410,7 +422,9 @@ class LocalFileBasedNodeStorage(NodeStorage):
certificate = self.__read_tls_public_certificate(checksum_address=checksum_address)
return certificate
metadata_path = self.__generate_metadata_filepath(checksum_address=checksum_address)
node = self.__read_metadata(filepath=metadata_path, federated_only=federated_only) # TODO: 466
node = self.__read_metadata(filepath=metadata_path,
blockchain=self.blockchain,
federated_only=federated_only) # TODO: 466
return node
def store_node_certificate(self, certificate: Certificate):