mirror of https://github.com/nucypher/nucypher.git
Ensure blockchain is injected into decentralized Characters, Addresses #1202
parent
4e3395726a
commit
f8db89d1a8
|
@ -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
|
||||
|
||||
#
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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):
|
||||
|
|
Loading…
Reference in New Issue