mirror of https://github.com/nucypher/nucypher.git
Merge pull request #1881 from nucypher/blue-oyster-mushrooms
Blue oyster mushrooms: Evolving node validity, optimization, domain separation, and migration.pull/2304/head
commit
9b45c08ac4
|
@ -75,7 +75,7 @@ label = b"secret/files/and/stuff"
|
|||
######################################
|
||||
|
||||
ALICE = Alice(network_middleware=RestMiddleware(),
|
||||
domains={TEMPORARY_DOMAIN},
|
||||
domain=TEMPORARY_DOMAIN,
|
||||
known_nodes=[ursula],
|
||||
learn_on_same_thread=True,
|
||||
federated_only=True)
|
||||
|
@ -87,7 +87,7 @@ ALICE = Alice(network_middleware=RestMiddleware(),
|
|||
policy_pubkey = ALICE.get_policy_encrypting_key_from_label(label)
|
||||
|
||||
BOB = Bob(known_nodes=[ursula],
|
||||
domains={TEMPORARY_DOMAIN},
|
||||
domain=TEMPORARY_DOMAIN,
|
||||
network_middleware=RestMiddleware(),
|
||||
federated_only=True,
|
||||
start_learning_now=True,
|
||||
|
|
|
@ -73,7 +73,7 @@ ursula = Ursula.from_seed_and_stake_info(seed_uri=SEEDNODE_URI,
|
|||
|
||||
alice_config = AliceConfiguration(
|
||||
config_root=os.path.join(TEMP_ALICE_DIR),
|
||||
domains={TEMPORARY_DOMAIN},
|
||||
domain=TEMPORARY_DOMAIN,
|
||||
known_nodes={ursula},
|
||||
start_learning_now=False,
|
||||
federated_only=True,
|
||||
|
|
|
@ -28,9 +28,9 @@ from umbral.keys import UmbralPublicKey
|
|||
|
||||
from nucypher.characters.lawful import Bob, Enrico, Ursula
|
||||
from nucypher.config.constants import TEMPORARY_DOMAIN
|
||||
from nucypher.crypto.keypairs import DecryptingKeypair, SigningKeypair
|
||||
from nucypher.crypto.kits import UmbralMessageKit
|
||||
from nucypher.crypto.powers import DecryptingPower, SigningPower
|
||||
from nucypher.datastore.keypairs import DecryptingKeypair, SigningKeypair
|
||||
from nucypher.network.middleware import RestMiddleware
|
||||
from nucypher.utilities.logging import GlobalLoggerSettings
|
||||
|
||||
|
@ -70,7 +70,7 @@ power_ups = [enc_power, sig_power]
|
|||
print("Creating the Doctor ...")
|
||||
|
||||
doctor = Bob(
|
||||
domains={TEMPORARY_DOMAIN},
|
||||
domain=TEMPORARY_DOMAIN,
|
||||
federated_only=True,
|
||||
crypto_power_ups=power_ups,
|
||||
start_learning_now=True,
|
||||
|
|
|
@ -15,19 +15,6 @@
|
|||
along with nucypher. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
"""
|
||||
This file is part of nucypher.
|
||||
nucypher is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
nucypher is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with nucypher. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
import os
|
||||
from contextlib import suppress
|
||||
from functools import partial
|
||||
|
@ -43,8 +30,7 @@ DEMO_NODE_STARTING_PORT = 11500
|
|||
|
||||
ursula_maker = partial(Ursula, rest_host='127.0.0.1',
|
||||
federated_only=True,
|
||||
domains=[TEMPORARY_DOMAIN],
|
||||
)
|
||||
domain=TEMPORARY_DOMAIN)
|
||||
|
||||
|
||||
def spin_up_federated_ursulas(quantity: int = FLEET_POPULATION):
|
||||
|
@ -54,7 +40,7 @@ def spin_up_federated_ursulas(quantity: int = FLEET_POPULATION):
|
|||
|
||||
ursulas = []
|
||||
|
||||
sage = ursula_maker(rest_port=ports[0], db_filepath=f"{Path(APP_DIR.user_cache_dir) / 'sage.db'}")
|
||||
sage = ursula_maker(rest_port=ports[0], db_filepath=str(Path(APP_DIR.user_cache_dir) / 'sage.db'))
|
||||
|
||||
ursulas.append(sage)
|
||||
for index, port in enumerate(ports[1:]):
|
||||
|
|
|
@ -105,7 +105,10 @@ class BaseActor:
|
|||
pass
|
||||
|
||||
@validate_checksum_address
|
||||
def __init__(self, registry: BaseContractRegistry, domains=None, checksum_address: ChecksumAddress = None):
|
||||
def __init__(self,
|
||||
registry: BaseContractRegistry,
|
||||
domain: Optional[str] = None,
|
||||
checksum_address: Optional[ChecksumAddress] = None):
|
||||
|
||||
# TODO: Consider this pattern - None for address?. #1507
|
||||
# Note: If the base class implements multiple inheritance and already has a checksum address...
|
||||
|
@ -118,8 +121,8 @@ class BaseActor:
|
|||
self.checksum_address = checksum_address # type: ChecksumAddress
|
||||
|
||||
self.registry = registry
|
||||
if domains: # StakeHolder config inherits from character config, which has 'domains' - #1580
|
||||
self.network = list(domains)[0]
|
||||
if domain: # StakeHolder config inherits from character config, which has 'domains' - See #1580
|
||||
self.network = domain
|
||||
|
||||
self._saved_receipts = list() # track receipts of transmitted transactions
|
||||
|
||||
|
@ -2192,7 +2195,7 @@ class DaoActor(BaseActor):
|
|||
signer: Signer = None,
|
||||
client_password: str = None,
|
||||
transacting: bool = True):
|
||||
super().__init__(registry=registry, domains=[network], checksum_address=checksum_address) # TODO: See #1580
|
||||
super().__init__(registry=registry, domain=network, checksum_address=checksum_address)
|
||||
self.dao_registry = DAORegistry(network=network)
|
||||
if transacting: # TODO: This logic is repeated in Bidder and possible others.
|
||||
self.transacting_power = TransactingPower(signer=signer,
|
||||
|
|
|
@ -58,7 +58,7 @@ class Character(Learner):
|
|||
from nucypher.network.protocols import SuspiciousActivity # Ship this exception with every Character.
|
||||
|
||||
def __init__(self,
|
||||
domains: Set = None,
|
||||
domain: str = None,
|
||||
known_node_class: object = None,
|
||||
is_me: bool = True,
|
||||
federated_only: bool = False,
|
||||
|
@ -171,8 +171,7 @@ class Character(Learner):
|
|||
#
|
||||
self.provider_uri = provider_uri
|
||||
if not self.federated_only:
|
||||
self.registry = registry or InMemoryContractRegistry.from_latest_publication(
|
||||
network=list(domains)[0]) # TODO: #1580
|
||||
self.registry = registry or InMemoryContractRegistry.from_latest_publication(network=domain) # See #1580
|
||||
else:
|
||||
self.registry = NO_BLOCKCHAIN_CONNECTION.bool_value(False)
|
||||
|
||||
|
@ -183,7 +182,7 @@ class Character(Learner):
|
|||
# Learner
|
||||
#
|
||||
Learner.__init__(self,
|
||||
domains=domains,
|
||||
domain=domain,
|
||||
network_middleware=self.network_middleware,
|
||||
node_class=known_node_class,
|
||||
*args, **kwargs)
|
||||
|
@ -343,6 +342,7 @@ class Character(Learner):
|
|||
# If we're federated only, we assume that all other nodes in our domain are as well.
|
||||
known_node_class.set_federated_mode(federated_only)
|
||||
|
||||
# TODO: Unused
|
||||
def store_metadata(self, filepath: str) -> str:
|
||||
"""
|
||||
Save this node to the disk.
|
||||
|
|
|
@ -51,6 +51,7 @@ from nucypher.acumen.nicknames import nickname_from_seed
|
|||
from nucypher.acumen.perception import FleetSensor
|
||||
from nucypher.blockchain.eth.actors import BlockchainPolicyAuthor, Worker
|
||||
from nucypher.blockchain.eth.agents import ContractAgency, StakingEscrowAgent
|
||||
from nucypher.blockchain.eth.constants import LENGTH_ECDSA_SIGNATURE_WITH_RECOVERY, ETH_ADDRESS_BYTE_LENGTH
|
||||
from nucypher.blockchain.eth.interfaces import BlockchainInterfaceFactory
|
||||
from nucypher.blockchain.eth.registry import BaseContractRegistry
|
||||
from nucypher.blockchain.eth.signers.software import Web3Signer
|
||||
|
@ -65,12 +66,12 @@ from nucypher.characters.control.interfaces import AliceInterface, BobInterface,
|
|||
from nucypher.cli.processes import UrsulaCommandProtocol
|
||||
from nucypher.config.storages import ForgetfulNodeStorage, NodeStorage
|
||||
from nucypher.crypto.api import encrypt_and_sign, keccak_digest
|
||||
from nucypher.crypto.constants import PUBLIC_ADDRESS_LENGTH, PUBLIC_KEY_LENGTH
|
||||
from nucypher.crypto.constants import PUBLIC_KEY_LENGTH
|
||||
from nucypher.crypto.keypairs import HostingKeypair
|
||||
from nucypher.crypto.kits import UmbralMessageKit
|
||||
from nucypher.crypto.powers import DecryptingPower, DelegatingPower, PowerUpError, SigningPower, TransactingPower
|
||||
from nucypher.crypto.signing import InvalidSignature
|
||||
from nucypher.datastore.datastore import DatastoreTransactionError, RecordNotFound
|
||||
from nucypher.datastore.keypairs import HostingKeypair
|
||||
from nucypher.datastore.models import PolicyArrangement
|
||||
from nucypher.network.exceptions import NodeSeemsToBeDown
|
||||
from nucypher.network.middleware import RestMiddleware
|
||||
|
@ -979,7 +980,7 @@ class Ursula(Teacher, Character, Worker):
|
|||
# Ursula
|
||||
rest_host: str,
|
||||
rest_port: int,
|
||||
domains: Set = None, # For now, serving and learning domains will be the same.
|
||||
domain: str = None, # For now, serving and learning domain will be the same.
|
||||
certificate: Certificate = None,
|
||||
certificate_filepath: str = None,
|
||||
db_filepath: str = None,
|
||||
|
@ -1014,10 +1015,10 @@ class Ursula(Teacher, Character, Worker):
|
|||
# Character
|
||||
#
|
||||
|
||||
if domains is None:
|
||||
if domain is None:
|
||||
# TODO: Move defaults to configuration, Off character.
|
||||
from nucypher.config.node import CharacterConfiguration
|
||||
domains = {CharacterConfiguration.DEFAULT_DOMAIN}
|
||||
domain = CharacterConfiguration.DEFAULT_DOMAIN
|
||||
|
||||
if is_me:
|
||||
# If we're federated only, we assume that all other nodes in our domain are as well.
|
||||
|
@ -1032,7 +1033,7 @@ class Ursula(Teacher, Character, Worker):
|
|||
crypto_power=crypto_power,
|
||||
abort_on_learning_error=abort_on_learning_error,
|
||||
known_nodes=known_nodes,
|
||||
domains=domains,
|
||||
domain=domain,
|
||||
known_node_class=Ursula,
|
||||
**character_kwargs)
|
||||
|
||||
|
@ -1104,7 +1105,7 @@ class Ursula(Teacher, Character, Worker):
|
|||
rest_app, datastore = make_rest_app(
|
||||
this_node=self,
|
||||
db_filepath=db_filepath,
|
||||
serving_domains=domains,
|
||||
serving_domain=domain,
|
||||
)
|
||||
|
||||
# TLSHostingPower (Ephemeral Powers and Private Keys)
|
||||
|
@ -1148,7 +1149,7 @@ class Ursula(Teacher, Character, Worker):
|
|||
certificate_filepath = self._crypto_power.power_ups(TLSHostingPower).keypair.certificate_filepath
|
||||
certificate = self._crypto_power.power_ups(TLSHostingPower).keypair.certificate
|
||||
Teacher.__init__(self,
|
||||
domains=domains,
|
||||
domain=domain,
|
||||
certificate=certificate,
|
||||
certificate_filepath=certificate_filepath,
|
||||
interface_signature=interface_signature,
|
||||
|
@ -1214,7 +1215,7 @@ class Ursula(Teacher, Character, Worker):
|
|||
# if learning: # TODO: Include learning startup here with the rest of the services?
|
||||
# self.start_learning_loop(now=self._start_learning_now)
|
||||
# if emitter:
|
||||
# emitter.message(f"✓ Node Discovery ({','.join(self.learning_domains)})", color='green')
|
||||
# emitter.message(f"✓ Node Discovery ({','.join(self.learning_domain)})", color='green')
|
||||
|
||||
if self._availability_check and availability:
|
||||
self._availability_tracker.start(now=False) # wait...
|
||||
|
@ -1323,18 +1324,16 @@ class Ursula(Teacher, Character, Worker):
|
|||
|
||||
version = self.TEACHER_VERSION.to_bytes(2, "big")
|
||||
interface_info = VariableLengthBytestring(bytes(self.rest_interface))
|
||||
decentralized_identity_evidence = VariableLengthBytestring(self.decentralized_identity_evidence) # TODO: Change to fixed length
|
||||
|
||||
certificate = self.rest_server_certificate()
|
||||
cert_vbytes = VariableLengthBytestring(certificate.public_bytes(Encoding.PEM))
|
||||
|
||||
domains = {domain.encode('utf-8') for domain in self.serving_domains}
|
||||
as_bytes = bytes().join((version,
|
||||
self.canonical_public_address,
|
||||
bytes(VariableLengthBytestring.bundle(domains)),
|
||||
bytes(VariableLengthBytestring(self.serving_domain.encode('utf-8'))),
|
||||
self.timestamp_bytes(),
|
||||
bytes(self._interface_signature),
|
||||
bytes(decentralized_identity_evidence),
|
||||
bytes(VariableLengthBytestring(self.decentralized_identity_evidence)), # FIXME: Fixed length doesn't work with federated
|
||||
bytes(self.public_keys(SigningPower)),
|
||||
bytes(self.public_keys(DecryptingPower)),
|
||||
bytes(cert_vbytes),
|
||||
|
@ -1464,15 +1463,15 @@ class Ursula(Teacher, Character, Worker):
|
|||
return potential_seed_node
|
||||
|
||||
@classmethod
|
||||
def internal_splitter(cls, splittable, partial=False):
|
||||
def payload_splitter(cls, splittable, partial=False):
|
||||
splitter = BytestringKwargifier(
|
||||
_receiver=cls.from_processed_bytes,
|
||||
_partial_receiver=NodeSprout,
|
||||
public_address=PUBLIC_ADDRESS_LENGTH,
|
||||
domains=VariableLengthBytestring, # TODO: Multiple domains? NRN
|
||||
public_address=ETH_ADDRESS_BYTE_LENGTH,
|
||||
domain=VariableLengthBytestring,
|
||||
timestamp=(int, 4, {'byteorder': 'big'}),
|
||||
interface_signature=Signature,
|
||||
decentralized_identity_evidence=VariableLengthBytestring,
|
||||
decentralized_identity_evidence=VariableLengthBytestring, # FIXME: Fixed length doesn't work with federated. It was LENGTH_ECDSA_SIGNATURE_WITH_RECOVERY,
|
||||
verifying_key=(UmbralPublicKey, PUBLIC_KEY_LENGTH),
|
||||
encrypting_key=(UmbralPublicKey, PUBLIC_KEY_LENGTH),
|
||||
certificate=(load_pem_x509_certificate, VariableLengthBytestring, {"backend": default_backend()}),
|
||||
|
@ -1481,6 +1480,10 @@ class Ursula(Teacher, Character, Worker):
|
|||
result = splitter(splittable, partial=partial)
|
||||
return result
|
||||
|
||||
@classmethod
|
||||
def is_compatible_version(cls, version: int) -> bool:
|
||||
return cls.LOWEST_COMPATIBLE_VERSION <= version <= cls.LEARNER_VERSION
|
||||
|
||||
@classmethod
|
||||
def from_bytes(cls,
|
||||
ursula_as_bytes: bytes,
|
||||
|
@ -1493,34 +1496,32 @@ class Ursula(Teacher, Character, Worker):
|
|||
else:
|
||||
payload = ursula_as_bytes
|
||||
|
||||
# Check version and raise IsFromTheFuture if this node is... you guessed it...
|
||||
if version > cls.LEARNER_VERSION:
|
||||
# Check version is compatible and prepare to handle potential failures otherwise
|
||||
if not cls.is_compatible_version(version):
|
||||
version_exception_class = cls.IsFromTheFuture if version > cls.LEARNER_VERSION else cls.AreYouFromThePast
|
||||
|
||||
# Try to handle failure, even during failure, graceful degradation
|
||||
# TODO: #154 - Some auto-updater logic?
|
||||
|
||||
try:
|
||||
canonical_address, _ = BytestringSplitter(PUBLIC_ADDRESS_LENGTH)(payload, return_remainder=True)
|
||||
canonical_address, _ = BytestringSplitter(ETH_ADDRESS_BYTE_LENGTH)(payload, return_remainder=True)
|
||||
checksum_address = to_checksum_address(canonical_address)
|
||||
nickname, _ = nickname_from_seed(checksum_address)
|
||||
display_name = cls._display_name_template.format(cls.__name__, nickname, checksum_address)
|
||||
message = cls.unknown_version_message.format(display_name, version, cls.LEARNER_VERSION)
|
||||
if version > cls.LEARNER_VERSION:
|
||||
message += " Is there a newer version of NuCypher?"
|
||||
except BytestringSplittingError:
|
||||
message = cls.really_unknown_version_message.format(version, cls.LEARNER_VERSION)
|
||||
if fail_fast:
|
||||
raise cls.IsFromTheFuture(message)
|
||||
else:
|
||||
cls.log.warn(message)
|
||||
return UNKNOWN_VERSION
|
||||
|
||||
if fail_fast:
|
||||
raise version_exception_class(message)
|
||||
else:
|
||||
if fail_fast:
|
||||
raise cls.IsFromTheFuture(message)
|
||||
else:
|
||||
cls.log.warn(message)
|
||||
return UNKNOWN_VERSION
|
||||
cls.log.warn(message)
|
||||
return UNKNOWN_VERSION
|
||||
else:
|
||||
# Version stuff checked out. Moving on.
|
||||
node_sprout = cls.internal_splitter(payload, partial=True)
|
||||
node_sprout = cls.payload_splitter(payload, partial=True)
|
||||
return node_sprout
|
||||
|
||||
@classmethod
|
||||
|
@ -1535,15 +1536,14 @@ class Ursula(Teacher, Character, Worker):
|
|||
rest_port = interface_info.port
|
||||
checksum_address = to_checksum_address(processed_objects.pop('public_address'))
|
||||
|
||||
domains_vbytes = VariableLengthBytestring.dispense(processed_objects.pop('domains'))
|
||||
domains = set(d.decode('utf-8') for d in domains_vbytes)
|
||||
domain = processed_objects.pop('domain').decode('utf-8')
|
||||
|
||||
timestamp = maya.MayaDT(processed_objects.pop('timestamp'))
|
||||
|
||||
ursula = cls.from_public_keys(rest_host=rest_host,
|
||||
rest_port=rest_port,
|
||||
checksum_address=checksum_address,
|
||||
domains=domains,
|
||||
domain=domain,
|
||||
timestamp=timestamp,
|
||||
**processed_objects)
|
||||
return ursula
|
||||
|
|
|
@ -73,7 +73,7 @@ class Vladimir(Ursula):
|
|||
vladimir = cls(is_me=True,
|
||||
crypto_power=crypto_power,
|
||||
db_filepath=cls.db_filepath,
|
||||
domains=[TEMPORARY_DOMAIN],
|
||||
domain=TEMPORARY_DOMAIN,
|
||||
block_until_ready=False,
|
||||
start_working_now=False,
|
||||
rest_host=target_ursula.rest_interface.host,
|
||||
|
|
|
@ -106,7 +106,7 @@ class AliceConfigOptions:
|
|||
provider_uri = eth_node.provider_uri(scheme='file')
|
||||
|
||||
self.dev = dev
|
||||
self.domains = {network} if network else None
|
||||
self.domain = network
|
||||
self.provider_uri = provider_uri
|
||||
self.signer_uri = signer_uri
|
||||
self.gas_strategy = gas_strategy
|
||||
|
@ -133,7 +133,7 @@ class AliceConfigOptions:
|
|||
emitter=emitter,
|
||||
dev_mode=True,
|
||||
network_middleware=self.middleware,
|
||||
domains={TEMPORARY_DOMAIN},
|
||||
domain=TEMPORARY_DOMAIN,
|
||||
provider_process=self.eth_node,
|
||||
provider_uri=self.provider_uri,
|
||||
signer_uri=self.signer_uri,
|
||||
|
@ -148,7 +148,7 @@ class AliceConfigOptions:
|
|||
emitter=emitter,
|
||||
dev_mode=False,
|
||||
network_middleware=self.middleware,
|
||||
domains=self.domains,
|
||||
domain=self.domain,
|
||||
provider_process=self.eth_node,
|
||||
provider_uri=self.provider_uri,
|
||||
signer_uri=self.signer_uri,
|
||||
|
@ -218,7 +218,7 @@ class AliceFullConfigOptions:
|
|||
password=get_nucypher_password(confirm=True),
|
||||
config_root=config_root,
|
||||
checksum_address=pay_with,
|
||||
domains=opts.domains,
|
||||
domain=opts.domain,
|
||||
federated_only=opts.federated_only,
|
||||
provider_uri=opts.provider_uri,
|
||||
signer_uri=opts.signer_uri,
|
||||
|
@ -233,7 +233,7 @@ class AliceFullConfigOptions:
|
|||
def get_updates(self) -> dict:
|
||||
opts = self.config_options
|
||||
payload = dict(checksum_address=opts.pay_with,
|
||||
domains=opts.domains,
|
||||
domain=opts.domain,
|
||||
federated_only=opts.federated_only,
|
||||
provider_uri=opts.provider_uri,
|
||||
signer_uri=opts.signer_uri,
|
||||
|
|
|
@ -79,7 +79,7 @@ class BobConfigOptions:
|
|||
self.provider_uri = provider_uri
|
||||
self.signer_uri = signer_uri
|
||||
self.gas_strategy = gas_strategy
|
||||
self.domains = {network} if network else None
|
||||
self.domain = network
|
||||
self.registry_filepath = registry_filepath
|
||||
self.checksum_address = checksum_address
|
||||
self.discovery_port = discovery_port
|
||||
|
@ -93,7 +93,7 @@ class BobConfigOptions:
|
|||
return BobConfiguration(
|
||||
emitter=emitter,
|
||||
dev_mode=True,
|
||||
domains={TEMPORARY_DOMAIN},
|
||||
domain=TEMPORARY_DOMAIN,
|
||||
provider_uri=self.provider_uri,
|
||||
gas_strategy=self.gas_strategy, # TODO: Fix type hint
|
||||
signer_uri=self.signer_uri,
|
||||
|
@ -107,7 +107,7 @@ class BobConfigOptions:
|
|||
return BobConfiguration.from_configuration_file(
|
||||
emitter=emitter,
|
||||
filepath=config_file,
|
||||
domains=self.domains,
|
||||
domain=self.domain,
|
||||
checksum_address=self.checksum_address,
|
||||
rest_port=self.discovery_port,
|
||||
provider_uri=self.provider_uri,
|
||||
|
@ -133,7 +133,7 @@ class BobConfigOptions:
|
|||
password=get_nucypher_password(confirm=True),
|
||||
config_root=config_root,
|
||||
checksum_address=checksum_address,
|
||||
domains=self.domains,
|
||||
domain=self.domain,
|
||||
federated_only=self.federated_only,
|
||||
registry_filepath=self.registry_filepath,
|
||||
provider_uri=self.provider_uri,
|
||||
|
@ -144,7 +144,7 @@ class BobConfigOptions:
|
|||
|
||||
def get_updates(self) -> dict:
|
||||
payload = dict(checksum_address=self.checksum_address,
|
||||
domains=self.domains,
|
||||
domain=self.domain,
|
||||
federated_only=self.federated_only,
|
||||
registry_filepath=self.registry_filepath,
|
||||
provider_uri=self.provider_uri,
|
||||
|
|
|
@ -87,7 +87,7 @@ class FelixConfigOptions:
|
|||
self.eth_node = eth_node
|
||||
self.provider_uri = provider_uri
|
||||
self.signer_uri = signer_uri
|
||||
self.domains = {network} if network else None
|
||||
self.domain = network
|
||||
self.dev = dev
|
||||
self.host = host
|
||||
self.db_filepath = db_filepath
|
||||
|
@ -102,7 +102,7 @@ class FelixConfigOptions:
|
|||
return FelixConfiguration.from_configuration_file(
|
||||
emitter=emitter,
|
||||
filepath=config_file,
|
||||
domains=self.domains,
|
||||
domain=self.domain,
|
||||
registry_filepath=self.registry_filepath,
|
||||
provider_process=self.eth_node,
|
||||
provider_uri=self.provider_uri,
|
||||
|
@ -124,7 +124,7 @@ class FelixConfigOptions:
|
|||
rest_host=self.host,
|
||||
rest_port=discovery_port,
|
||||
db_filepath=self.db_filepath,
|
||||
domains=self.domains,
|
||||
domain=self.domain,
|
||||
checksum_address=self.checksum_address,
|
||||
registry_filepath=self.registry_filepath,
|
||||
provider_uri=self.provider_uri,
|
||||
|
@ -173,7 +173,7 @@ class FelixCharacterOptions:
|
|||
envvar=NUCYPHER_ENVVAR_WORKER_ETH_PASSWORD)
|
||||
|
||||
# Produce Felix
|
||||
FELIX = felix_config.produce(domains=self.config_options.domains, client_password=client_password)
|
||||
FELIX = felix_config.produce(domain=self.config_options.domain, client_password=client_password)
|
||||
FELIX.make_web_app() # attach web application, but dont start service
|
||||
|
||||
return FELIX
|
||||
|
|
|
@ -133,7 +133,7 @@ class StakeHolderConfigOptions:
|
|||
poa=self.poa,
|
||||
light=self.light,
|
||||
sync=False,
|
||||
domains={self.network} if self.network else None, # TODO: #1580
|
||||
domain=self.network,
|
||||
registry_filepath=self.registry_filepath)
|
||||
|
||||
except FileNotFoundError:
|
||||
|
@ -162,7 +162,7 @@ class StakeHolderConfigOptions:
|
|||
light=self.light,
|
||||
sync=False,
|
||||
registry_filepath=self.registry_filepath,
|
||||
domains={self.network} # TODO: #1580
|
||||
domain=self.network
|
||||
)
|
||||
|
||||
def get_updates(self) -> dict:
|
||||
|
@ -171,7 +171,7 @@ class StakeHolderConfigOptions:
|
|||
poa=self.poa,
|
||||
light=self.light,
|
||||
registry_filepath=self.registry_filepath,
|
||||
domains={self.network} if self.network else None) # TODO: #1580
|
||||
domain=self.network)
|
||||
# Depends on defaults being set on Configuration classes, filtrates None values
|
||||
updates = {k: v for k, v in payload.items() if v is not None}
|
||||
return updates
|
||||
|
@ -232,7 +232,7 @@ class TransactingStakerOptions:
|
|||
is_preallocation_staker = (self.beneficiary_address and opts.staking_address) or self.allocation_filepath
|
||||
|
||||
if is_preallocation_staker:
|
||||
network = opts.config_options.network or list(stakeholder_config.domains)[0] # TODO: 1580 - ugly network/domains mapping
|
||||
network = opts.config_options.network or stakeholder_config.domain
|
||||
if self.allocation_filepath:
|
||||
if self.beneficiary_address or opts.staking_address:
|
||||
message = "--allocation-filepath is incompatible with --beneficiary-address and --staking-address."
|
||||
|
|
|
@ -120,7 +120,7 @@ class UrsulaConfigOptions:
|
|||
self.rest_host = rest_host
|
||||
self.rest_port = rest_port # FIXME: not used in generate()
|
||||
self.db_filepath = db_filepath
|
||||
self.domains = {network} if network else None # TODO: #1580
|
||||
self.domain = network
|
||||
self.registry_filepath = registry_filepath
|
||||
self.dev = dev
|
||||
self.poa = poa
|
||||
|
@ -134,7 +134,7 @@ class UrsulaConfigOptions:
|
|||
return UrsulaConfiguration(
|
||||
emitter=emitter,
|
||||
dev_mode=True,
|
||||
domains={TEMPORARY_DOMAIN},
|
||||
domain=TEMPORARY_DOMAIN,
|
||||
poa=self.poa,
|
||||
light=self.light,
|
||||
registry_filepath=self.registry_filepath,
|
||||
|
@ -154,7 +154,7 @@ class UrsulaConfigOptions:
|
|||
return UrsulaConfiguration.from_configuration_file(
|
||||
emitter=emitter,
|
||||
filepath=config_file,
|
||||
domains=self.domains,
|
||||
domain=self.domain,
|
||||
registry_filepath=self.registry_filepath,
|
||||
provider_process=self.eth_node,
|
||||
provider_uri=self.provider_uri,
|
||||
|
@ -202,7 +202,7 @@ class UrsulaConfigOptions:
|
|||
rest_host=rest_host,
|
||||
rest_port=self.rest_port,
|
||||
db_filepath=self.db_filepath,
|
||||
domains=self.domains,
|
||||
domain=self.domain,
|
||||
federated_only=self.federated_only,
|
||||
worker_address=worker_address,
|
||||
registry_filepath=self.registry_filepath,
|
||||
|
@ -218,7 +218,7 @@ class UrsulaConfigOptions:
|
|||
payload = dict(rest_host=self.rest_host,
|
||||
rest_port=self.rest_port,
|
||||
db_filepath=self.db_filepath,
|
||||
domains=self.domains,
|
||||
domain=self.domain,
|
||||
federated_only=self.federated_only,
|
||||
checksum_address=self.worker_address,
|
||||
registry_filepath=self.registry_filepath,
|
||||
|
@ -319,8 +319,8 @@ def init(general_config, config_options, force, config_root):
|
|||
_pre_launch_warnings(emitter, dev=None, force=force)
|
||||
if not config_root:
|
||||
config_root = general_config.config_root
|
||||
if not config_options.federated_only and not config_options.domains: # TODO: Again, weird network/domains mapping. See UrsulaConfigOptions' constructor. #1580
|
||||
config_options.domains = {select_network(emitter)}
|
||||
if not config_options.federated_only and not config_options.domain:
|
||||
config_options.domain = select_network(emitter)
|
||||
ursula_config = config_options.generate_config(emitter, config_root, force)
|
||||
paint_new_installation_help(emitter, new_configuration=ursula_config)
|
||||
|
||||
|
|
|
@ -374,7 +374,7 @@ UNREADABLE_SEEDNODE_ADVISORY = "Failed to connect to teacher: {uri}"
|
|||
|
||||
FORCE_DETECT_URSULA_IP_WARNING = "WARNING: --force is set, using auto-detected IP '{rest_host}'"
|
||||
|
||||
NO_DOMAIN_PEERS = "WARNING: No Peers Available for domains: {domains}"
|
||||
NO_DOMAIN_PEERS = "WARNING: No Peers Available for domain: {domain}"
|
||||
|
||||
SEEDNODE_NOT_STAKING_WARNING = "Teacher ({uri}) is not actively staking, skipping"
|
||||
|
||||
|
|
|
@ -19,10 +19,8 @@ from collections import namedtuple
|
|||
|
||||
import click
|
||||
import functools
|
||||
import os
|
||||
|
||||
from nucypher.blockchain.eth.constants import NUCYPHER_CONTRACT_NAMES
|
||||
from nucypher.blockchain.eth.networks import NetworksInventory
|
||||
from nucypher.cli.types import (
|
||||
EIP55_CHECKSUM_ADDRESS,
|
||||
EXISTING_READABLE_FILE,
|
||||
|
|
|
@ -263,7 +263,7 @@ class StakeHolderConfiguration(CharacterConfiguration):
|
|||
payload = dict(provider_uri=self.provider_uri,
|
||||
poa=self.poa,
|
||||
light=self.is_light,
|
||||
domains=list(self.domains),
|
||||
domain=self.domain,
|
||||
# TODO: Move empty collection casting to base
|
||||
checksum_addresses=self.checksum_addresses or list(),
|
||||
signer_uri=self.signer_uri)
|
||||
|
@ -274,7 +274,7 @@ class StakeHolderConfiguration(CharacterConfiguration):
|
|||
|
||||
@property
|
||||
def dynamic_payload(self) -> dict:
|
||||
testnet = NetworksInventory.MAINNET not in self.domains # TODO: use equality instead of membership after blue oysters
|
||||
testnet = self.domain != NetworksInventory.MAINNET
|
||||
signer = Signer.from_signer_uri(self.signer_uri, testnet=testnet)
|
||||
payload = dict(registry=self.registry, signer=signer)
|
||||
return payload
|
||||
|
|
|
@ -55,7 +55,7 @@ class CharacterConfiguration(BaseConfiguration):
|
|||
'Sideways Engagement' of Character classes; a reflection of input parameters.
|
||||
"""
|
||||
|
||||
VERSION = 1 # bump when static payload scheme changes
|
||||
VERSION = 2 # bump when static payload scheme changes
|
||||
|
||||
CHARACTER_CLASS = NotImplemented
|
||||
DEFAULT_CONTROLLER_PORT = NotImplemented
|
||||
|
@ -95,7 +95,7 @@ class CharacterConfiguration(BaseConfiguration):
|
|||
|
||||
# Network
|
||||
controller_port: int = None,
|
||||
domains: Set[str] = None, # TODO: Mapping between learning domains and "registry" domains - #1580
|
||||
domain: str = DEFAULT_DOMAIN,
|
||||
interface_signature: Signature = None,
|
||||
network_middleware: RestMiddleware = None,
|
||||
lonely: bool = False,
|
||||
|
@ -151,7 +151,7 @@ class CharacterConfiguration(BaseConfiguration):
|
|||
|
||||
# Learner
|
||||
self.federated_only = federated_only
|
||||
self.domains = domains or {self.DEFAULT_DOMAIN}
|
||||
self.domain = domain
|
||||
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
|
||||
|
@ -216,7 +216,7 @@ class CharacterConfiguration(BaseConfiguration):
|
|||
# TODO: These two code blocks are untested.
|
||||
if not self.registry_filepath: # TODO: Registry URI (goerli://speedynet.json) :-)
|
||||
self.log.info(f"Fetching latest registry from source.")
|
||||
self.registry = InMemoryContractRegistry.from_latest_publication(network=list(self.domains)[0]) # TODO: #1580
|
||||
self.registry = InMemoryContractRegistry.from_latest_publication(network=self.domain)
|
||||
else:
|
||||
self.registry = LocalContractRegistry(filepath=self.registry_filepath)
|
||||
self.log.info(f"Using local registry ({self.registry}).")
|
||||
|
@ -352,10 +352,10 @@ class CharacterConfiguration(BaseConfiguration):
|
|||
payload = cls._read_configuration_file(filepath=filepath)
|
||||
node_storage = cls.load_node_storage(storage_payload=payload['node_storage'],
|
||||
federated_only=payload['federated_only'])
|
||||
domains = set(payload['domains'])
|
||||
domain = payload['domain']
|
||||
|
||||
# Assemble
|
||||
payload.update(dict(node_storage=node_storage, domains=domains))
|
||||
payload.update(dict(node_storage=node_storage, domain=domain))
|
||||
# Filter out None values from **overrides to detect, well, overrides...
|
||||
# Acts as a shim for optional CLI flags.
|
||||
overrides = {k: v for k, v in overrides.items() if v is not None}
|
||||
|
@ -401,7 +401,7 @@ class CharacterConfiguration(BaseConfiguration):
|
|||
keyring_root=self.keyring_root,
|
||||
|
||||
# Behavior
|
||||
domains=list(self.domains), # From Set
|
||||
domain=self.domain,
|
||||
learn_on_same_thread=self.learn_on_same_thread,
|
||||
abort_on_learning_error=self.abort_on_learning_error,
|
||||
start_learning_now=self.start_learning_now,
|
||||
|
@ -436,7 +436,7 @@ class CharacterConfiguration(BaseConfiguration):
|
|||
"""Exported dynamic configuration values for initializing Ursula"""
|
||||
payload = dict()
|
||||
if not self.federated_only:
|
||||
testnet = NetworksInventory.MAINNET not in self.domains # TODO: use equality, not membership after blue oyster mushrooms
|
||||
testnet = self.domain != NetworksInventory.MAINNET
|
||||
signer = Signer.from_signer_uri(self.signer_uri, testnet=testnet)
|
||||
payload.update(dict(registry=self.registry, signer=signer))
|
||||
|
||||
|
|
|
@ -15,13 +15,15 @@ You should have received a copy of the GNU Affero General Public License
|
|||
along with nucypher. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
import sqlite3
|
||||
from pathlib import Path
|
||||
|
||||
import OpenSSL
|
||||
import binascii
|
||||
import os
|
||||
import tempfile
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
from bytestring_splitter import BytestringSplittingError
|
||||
from cryptography import x509
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
from cryptography.hazmat.primitives.serialization import Encoding
|
||||
|
@ -33,15 +35,14 @@ from nucypher.acumen.nicknames import nickname_from_seed
|
|||
from nucypher.blockchain.eth.decorators import validate_checksum_address
|
||||
from nucypher.blockchain.eth.registry import BaseContractRegistry
|
||||
from nucypher.config.constants import DEFAULT_CONFIG_ROOT
|
||||
from nucypher.crypto.api import read_certificate_pseudonym
|
||||
from nucypher.crypto.api import read_certificate_pseudonym, InvalidNodeCertificate
|
||||
from nucypher.utilities.logging import Logger
|
||||
|
||||
|
||||
class NodeStorage(ABC):
|
||||
_name = NotImplemented
|
||||
_TYPE_LABEL = 'storage_type'
|
||||
NODE_SERIALIZER = binascii.hexlify
|
||||
NODE_DESERIALIZER = binascii.unhexlify
|
||||
|
||||
TLS_CERTIFICATE_ENCODING = Encoding.PEM
|
||||
TLS_CERTIFICATE_EXTENSION = '.{}'.format(TLS_CERTIFICATE_ENCODING.name.lower())
|
||||
|
||||
|
@ -51,14 +52,9 @@ class NodeStorage(ABC):
|
|||
class UnknownNode(NodeStorageError):
|
||||
pass
|
||||
|
||||
class InvalidNodeCertificate(RuntimeError):
|
||||
"""Raised when a TLS certificate is not a valid Teacher certificate."""
|
||||
|
||||
def __init__(self,
|
||||
federated_only: bool, # TODO# 466
|
||||
character_class=None,
|
||||
serializer: Callable = NODE_SERIALIZER,
|
||||
deserializer: Callable = NODE_DESERIALIZER,
|
||||
registry: BaseContractRegistry = None,
|
||||
) -> None:
|
||||
|
||||
|
@ -66,8 +62,6 @@ class NodeStorage(ABC):
|
|||
|
||||
self.log = Logger(self.__class__.__name__)
|
||||
self.registry = registry
|
||||
self.serializer = serializer
|
||||
self.deserializer = deserializer
|
||||
self.federated_only = federated_only
|
||||
self.character_class = character_class or Ursula
|
||||
|
||||
|
@ -89,6 +83,12 @@ class NodeStorage(ABC):
|
|||
"""Human readable source string"""
|
||||
return NotImplemented
|
||||
|
||||
def encode_node_bytes(self, node_bytes):
|
||||
return binascii.hexlify(node_bytes)
|
||||
|
||||
def decode_node_bytes(self, encoded_node) -> bytes:
|
||||
return binascii.unhexlify(encoded_node)
|
||||
|
||||
def _read_common_name(self, certificate: Certificate):
|
||||
x509 = OpenSSL.crypto.X509.from_cryptography(certificate)
|
||||
subject_components = x509.get_subject().get_components()
|
||||
|
@ -112,13 +112,13 @@ class NodeStorage(ABC):
|
|||
try:
|
||||
pseudonym = certificate.subject.get_attributes_for_oid(NameOID.PSEUDONYM)[0]
|
||||
except IndexError:
|
||||
raise self.InvalidNodeCertificate(f"Missing checksum address on certificate for host '{host}'. "
|
||||
f"Does this certificate belong to an Ursula?")
|
||||
raise InvalidNodeCertificate(f"Missing checksum address on certificate for host '{host}'. "
|
||||
f"Does this certificate belong to an Ursula?")
|
||||
else:
|
||||
checksum_address = pseudonym.value
|
||||
|
||||
if not is_checksum_address(checksum_address):
|
||||
raise self.InvalidNodeCertificate("Invalid certificate wallet address encountered: {}".format(checksum_address))
|
||||
raise InvalidNodeCertificate("Invalid certificate wallet address encountered: {}".format(checksum_address))
|
||||
|
||||
# Validate
|
||||
# TODO: It's better for us to have checked this a while ago so that this situation is impossible. #443
|
||||
|
@ -201,7 +201,7 @@ class ForgetfulNodeStorage(NodeStorage):
|
|||
# Certificates
|
||||
self.__certificates = dict()
|
||||
self.__temporary_certificates = list()
|
||||
self._temp_certificates_dir = tempfile.mkdtemp(prefix='nucypher-temp-certs-', dir=parent_dir)
|
||||
self._temp_certificates_dir = tempfile.mkdtemp(prefix=self.__base_prefix, dir=parent_dir)
|
||||
|
||||
@property
|
||||
def source(self) -> str:
|
||||
|
@ -209,7 +209,7 @@ class ForgetfulNodeStorage(NodeStorage):
|
|||
return self._name
|
||||
|
||||
def all(self, federated_only: bool, certificates_only: bool = False) -> set:
|
||||
return set(self.__metadata.values() if not certificates_only else self.__certificates.values())
|
||||
return set(self.__certificates.values() if certificates_only else self.__metadata.values())
|
||||
|
||||
@validate_checksum_address
|
||||
def get(self,
|
||||
|
@ -285,11 +285,9 @@ class ForgetfulNodeStorage(NodeStorage):
|
|||
raise cls.NodeStorageError
|
||||
return cls(*args, **kwargs)
|
||||
|
||||
def initialize(self) -> bool:
|
||||
"""Returns True if initialization was successful"""
|
||||
def initialize(self):
|
||||
self.__metadata = dict()
|
||||
self.__certificates = dict()
|
||||
return not bool(self.__metadata or self.__certificates)
|
||||
|
||||
|
||||
class LocalFileBasedNodeStorage(NodeStorage):
|
||||
|
@ -299,6 +297,9 @@ class LocalFileBasedNodeStorage(NodeStorage):
|
|||
class NoNodeMetadataFileFound(FileNotFoundError, NodeStorage.UnknownNode):
|
||||
pass
|
||||
|
||||
class InvalidNodeMetadata(NodeStorage.NodeStorageError):
|
||||
"""Node metadata is corrupt or not possible to parse"""
|
||||
|
||||
def __init__(self,
|
||||
config_root: str = None,
|
||||
storage_root: str = None,
|
||||
|
@ -320,6 +321,12 @@ class LocalFileBasedNodeStorage(NodeStorage):
|
|||
"""Human readable source string"""
|
||||
return self.root_dir
|
||||
|
||||
def encode_node_bytes(self, node_bytes) -> bytes:
|
||||
return node_bytes
|
||||
|
||||
def decode_node_bytes(self, encoded_node) -> bytes:
|
||||
return encoded_node
|
||||
|
||||
@staticmethod
|
||||
def _generate_storage_filepaths(config_root: str = None,
|
||||
storage_root: str = None,
|
||||
|
@ -363,7 +370,7 @@ class LocalFileBasedNodeStorage(NodeStorage):
|
|||
return certificate_filepath
|
||||
|
||||
@validate_checksum_address
|
||||
def __read_tls_public_certificate(self, filepath: str = None, checksum_address: str = None) -> Certificate:
|
||||
def __read_node_tls_certificate(self, filepath: str = None, checksum_address: str = None) -> Certificate:
|
||||
"""Deserialize an X509 certificate from a filepath"""
|
||||
if not bool(filepath) ^ bool(checksum_address):
|
||||
raise ValueError("Either pass filepath or checksum_address; Not both.")
|
||||
|
@ -373,8 +380,12 @@ class LocalFileBasedNodeStorage(NodeStorage):
|
|||
|
||||
try:
|
||||
with open(filepath, 'rb') as certificate_file:
|
||||
cert = x509.load_pem_x509_certificate(certificate_file.read(), backend=default_backend())
|
||||
return cert
|
||||
certificate = x509.load_pem_x509_certificate(certificate_file.read(), backend=default_backend())
|
||||
# Sanity check:
|
||||
# Validate the checksum address inside the cert as a consistency check against
|
||||
# nodes that may have been altered on the disk somehow.
|
||||
read_certificate_pseudonym(certificate=certificate)
|
||||
return certificate
|
||||
except FileNotFoundError:
|
||||
raise FileNotFoundError("No SSL certificate found at {}".format(filepath))
|
||||
|
||||
|
@ -388,23 +399,26 @@ 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):
|
||||
|
||||
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)
|
||||
node_bytes = self.decode_node_bytes(seed_file.read())
|
||||
node = Ursula.from_bytes(node_bytes, fail_fast=True)
|
||||
except FileNotFoundError:
|
||||
raise self.UnknownNode
|
||||
raise self.NoNodeMetadataFileFound
|
||||
except (BytestringSplittingError, Ursula.UnexpectedVersion):
|
||||
raise self.InvalidNodeMetadata
|
||||
|
||||
return node
|
||||
|
||||
def __write_metadata(self, filepath: str, node):
|
||||
os.makedirs(os.path.dirname(filepath), exist_ok=True)
|
||||
with open(filepath, "wb") as f:
|
||||
f.write(self.serializer(bytes(node)))
|
||||
f.write(self.encode_node_bytes(bytes(node)))
|
||||
self.log.info("Wrote new node metadata to filesystem {}".format(filepath))
|
||||
return filepath
|
||||
|
||||
|
@ -418,25 +432,33 @@ class LocalFileBasedNodeStorage(NodeStorage):
|
|||
known_certificates = set()
|
||||
if certificates_only:
|
||||
for filename in filenames:
|
||||
certificate = self.__read_tls_public_certificate(os.path.join(self.certificates_dir, filename))
|
||||
certificate = self.__read_node_tls_certificate(os.path.join(self.certificates_dir, filename))
|
||||
known_certificates.add(certificate)
|
||||
return known_certificates
|
||||
|
||||
else:
|
||||
known_nodes = set()
|
||||
invalid_metadata = []
|
||||
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
|
||||
known_nodes.add(node)
|
||||
try:
|
||||
node = self.__read_metadata(filepath=metadata_path)
|
||||
except self.NodeStorageError:
|
||||
invalid_metadata.append(filename)
|
||||
else:
|
||||
known_nodes.add(node)
|
||||
|
||||
if invalid_metadata:
|
||||
self.log.warn(f"Couldn't read metadata in {self.metadata_dir} for the following files: {invalid_metadata}")
|
||||
return known_nodes
|
||||
|
||||
@validate_checksum_address
|
||||
def get(self, checksum_address: str, federated_only: bool, certificate_only: bool = False):
|
||||
if certificate_only is True:
|
||||
certificate = self.__read_tls_public_certificate(checksum_address=checksum_address)
|
||||
certificate = self.__read_node_tls_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)
|
||||
return node
|
||||
|
||||
def store_node_certificate(self, certificate: Certificate, force: bool = True):
|
||||
|
@ -449,11 +471,6 @@ class LocalFileBasedNodeStorage(NodeStorage):
|
|||
self.__write_metadata(filepath=filepath, node=node)
|
||||
return filepath
|
||||
|
||||
def save_node(self, node, force) -> Tuple[str, str]:
|
||||
certificate_filepath = self.store_node_certificate(certificate=node.certificate, force=force)
|
||||
metadata_filepath = self.store_node_metadata(node=node)
|
||||
return metadata_filepath, certificate_filepath
|
||||
|
||||
@validate_checksum_address
|
||||
def remove(self, checksum_address: str, metadata: bool = True, certificate: bool = True) -> None:
|
||||
|
||||
|
@ -508,7 +525,7 @@ class LocalFileBasedNodeStorage(NodeStorage):
|
|||
|
||||
return cls(*args, **payload, **kwargs)
|
||||
|
||||
def initialize(self) -> bool:
|
||||
def initialize(self):
|
||||
storage_dirs = (self.root_dir, self.metadata_dir, self.certificates_dir)
|
||||
for storage_dir in storage_dirs:
|
||||
try:
|
||||
|
@ -519,8 +536,6 @@ class LocalFileBasedNodeStorage(NodeStorage):
|
|||
except FileNotFoundError:
|
||||
raise self.NodeStorageError("There is no existing configuration at {}".format(self.root_dir))
|
||||
|
||||
return bool(all(map(os.path.isdir, (self.root_dir, self.metadata_dir, self.certificates_dir))))
|
||||
|
||||
|
||||
class TemporaryFileBasedNodeStorage(LocalFileBasedNodeStorage):
|
||||
_name = 'tmp'
|
||||
|
@ -528,8 +543,10 @@ class TemporaryFileBasedNodeStorage(LocalFileBasedNodeStorage):
|
|||
def __init__(self, *args, **kwargs):
|
||||
self.__temp_metadata_dir = None
|
||||
self.__temp_certificates_dir = None
|
||||
self.__temp_root_dir = None
|
||||
super().__init__(metadata_dir=self.__temp_metadata_dir,
|
||||
certificates_dir=self.__temp_certificates_dir,
|
||||
storage_root=self.__temp_root_dir,
|
||||
*args, **kwargs)
|
||||
|
||||
# TODO: Pending fix for 1554.
|
||||
|
@ -538,20 +555,15 @@ class TemporaryFileBasedNodeStorage(LocalFileBasedNodeStorage):
|
|||
# shutil.rmtree(self.__temp_metadata_dir, ignore_errors=True)
|
||||
# shutil.rmtree(self.__temp_certificates_dir, ignore_errors=True)
|
||||
|
||||
def initialize(self) -> bool:
|
||||
def initialize(self):
|
||||
# Root
|
||||
self.__temp_root_dir = tempfile.mkdtemp(prefix="nucypher-tmp-nodes-")
|
||||
self.root_dir = self.__temp_root_dir
|
||||
|
||||
# Metadata
|
||||
self.__temp_metadata_dir = tempfile.mkdtemp(prefix="nucypher-tmp-nodes-")
|
||||
self.__temp_metadata_dir = str(Path(self.__temp_root_dir) / "metadata")
|
||||
self.metadata_dir = self.__temp_metadata_dir
|
||||
|
||||
# Certificates
|
||||
self.__temp_certificates_dir = tempfile.mkdtemp(prefix="nucypher-tmp-certs-")
|
||||
self.__temp_certificates_dir = str(Path(self.__temp_root_dir) / "certs")
|
||||
self.certificates_dir = self.__temp_certificates_dir
|
||||
|
||||
return bool(os.path.isdir(self.metadata_dir) and os.path.isdir(self.certificates_dir))
|
||||
|
||||
|
||||
#
|
||||
# Node Storage Registry
|
||||
#
|
||||
NODE_STORAGES = {storage_class._name: storage_class
|
||||
for storage_class in NodeStorage.__subclasses__()}
|
||||
|
|
|
@ -44,6 +44,10 @@ from nucypher.crypto.kits import UmbralMessageKit
|
|||
SYSTEM_RAND = SystemRandom()
|
||||
|
||||
|
||||
class InvalidNodeCertificate(RuntimeError):
|
||||
"""Raised when an Ursula's certificate is not valid because it is missing the checksum address."""
|
||||
|
||||
|
||||
def secure_random(num_bytes: int) -> bytes:
|
||||
"""
|
||||
Returns an amount `num_bytes` of data from the OS's random device.
|
||||
|
@ -219,13 +223,14 @@ def generate_self_signed_certificate(*args, **kwargs):
|
|||
|
||||
|
||||
def read_certificate_pseudonym(certificate: Certificate):
|
||||
"""Return the checksum address written into a TLS certificates pseudonym field or raise an error."""
|
||||
try:
|
||||
pseudonym = certificate.subject.get_attributes_for_oid(NameOID.PSEUDONYM)[0]
|
||||
except IndexError:
|
||||
raise RuntimeError("Invalid teacher certificate encountered: No checksum address present as pseudonym.")
|
||||
raise InvalidNodeCertificate("Invalid teacher certificate encountered: No checksum address present as pseudonym.")
|
||||
checksum_address = pseudonym.value
|
||||
if not is_checksum_address(checksum_address):
|
||||
raise RuntimeError("Invalid certificate checksum_address encountered")
|
||||
raise InvalidNodeCertificate("Invalid certificate checksum address encountered")
|
||||
return checksum_address
|
||||
|
||||
|
||||
|
|
|
@ -29,4 +29,3 @@ BLAKE2B = hashes.BLAKE2b(64)
|
|||
# SECP256K1
|
||||
CAPSULE_LENGTH = 98
|
||||
PUBLIC_KEY_LENGTH = 33
|
||||
PUBLIC_ADDRESS_LENGTH = 20
|
||||
|
|
|
@ -26,8 +26,8 @@ from nucypher.blockchain.eth.decorators import validate_checksum_address
|
|||
from nucypher.blockchain.eth.interfaces import BlockchainInterface, BlockchainInterfaceFactory
|
||||
from nucypher.blockchain.eth.signers.software import Web3Signer
|
||||
from nucypher.blockchain.eth.signers.base import Signer
|
||||
from nucypher.datastore import keypairs
|
||||
from nucypher.datastore.keypairs import DecryptingKeypair, SigningKeypair
|
||||
from nucypher.crypto import keypairs
|
||||
from nucypher.crypto.keypairs import DecryptingKeypair, SigningKeypair
|
||||
|
||||
|
||||
class PowerUpError(TypeError):
|
||||
|
|
|
@ -14,4 +14,4 @@ GNU Affero General Public License for more details.
|
|||
You should have received a copy of the GNU Affero General Public License
|
||||
along with nucypher. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
LEARNING_LOOP_VERSION = 1
|
||||
LEARNING_LOOP_VERSION = 2 # TODO: Rename to DISCOVERY_LOOP_VERSION
|
||||
|
|
|
@ -21,7 +21,7 @@ import time
|
|||
from collections import defaultdict, deque
|
||||
from contextlib import suppress
|
||||
from queue import Queue
|
||||
from typing import Iterable
|
||||
from typing import Iterable, List
|
||||
from typing import Set, Tuple, Union
|
||||
|
||||
import maya
|
||||
|
@ -48,7 +48,7 @@ from nucypher.blockchain.eth.constants import NULL_ADDRESS
|
|||
from nucypher.blockchain.eth.registry import BaseContractRegistry
|
||||
from nucypher.config.constants import SeednodeMetadata
|
||||
from nucypher.config.storages import ForgetfulNodeStorage
|
||||
from nucypher.crypto.api import recover_address_eip_191, verify_eip_191
|
||||
from nucypher.crypto.api import recover_address_eip_191, verify_eip_191, InvalidNodeCertificate
|
||||
from nucypher.crypto.kits import UmbralMessageKit
|
||||
from nucypher.crypto.powers import DecryptingPower, NoSigningPower, SigningPower, TransactingPower
|
||||
from nucypher.crypto.signing import signature_splitter
|
||||
|
@ -163,13 +163,13 @@ class Learner:
|
|||
__DEFAULT_MIDDLEWARE_CLASS = RestMiddleware
|
||||
|
||||
LEARNER_VERSION = LEARNING_LOOP_VERSION
|
||||
LOWEST_COMPATIBLE_VERSION = 2 # Disallow versions lower than this
|
||||
|
||||
node_splitter = BytestringSplitter(VariableLengthBytestring)
|
||||
version_splitter = BytestringSplitter((int, 2, {"byteorder": "big"}))
|
||||
tracker_class = FleetSensor
|
||||
|
||||
invalid_metadata_message = "{} has invalid metadata. The node's stake may have ended, or it is transitioning to a new interface. Ignoring."
|
||||
unknown_version_message = "{} purported to be of version {}, but we're only version {}. Is there a new version of NuCypher?"
|
||||
really_unknown_version_message = "Unable to glean address from node that perhaps purported to be version {}. We're only version {}."
|
||||
fleet_state_icon = ""
|
||||
|
||||
_DEBUG_MODE = False
|
||||
|
@ -193,7 +193,7 @@ class Learner:
|
|||
pass
|
||||
|
||||
def __init__(self,
|
||||
domains: set,
|
||||
domain: str,
|
||||
node_class: object = None,
|
||||
network_middleware: RestMiddleware = None,
|
||||
start_learning_now: bool = False,
|
||||
|
@ -210,7 +210,7 @@ class Learner:
|
|||
self.log = Logger("learning-loop") # type: Logger
|
||||
|
||||
self.learning_deferred = Deferred()
|
||||
self.learning_domains = domains
|
||||
self.learning_domain = domain
|
||||
if not self.federated_only:
|
||||
default_middleware = self.__DEFAULT_MIDDLEWARE_CLASS(registry=self.registry)
|
||||
else:
|
||||
|
@ -293,10 +293,8 @@ class Learner:
|
|||
|
||||
discovered = []
|
||||
|
||||
if self.learning_domains:
|
||||
one_and_only_learning_domain = tuple(self.learning_domains)[
|
||||
0] # TODO: Are we done with multiple domains? 2144, 1580
|
||||
canonical_sage_uris = self.network_middleware.TEACHER_NODES.get(one_and_only_learning_domain, ())
|
||||
if self.learning_domain:
|
||||
canonical_sage_uris = self.network_middleware.TEACHER_NODES.get(self.learning_domain, ())
|
||||
|
||||
for uri in canonical_sage_uris:
|
||||
try:
|
||||
|
@ -338,9 +336,7 @@ class Learner:
|
|||
|
||||
self.done_seeding = True
|
||||
|
||||
if read_storage is True:
|
||||
nodes_restored_from_storage = self.read_nodes_from_storage()
|
||||
|
||||
nodes_restored_from_storage = self.read_nodes_from_storage() if read_storage else []
|
||||
discovered.extend(nodes_restored_from_storage)
|
||||
|
||||
if discovered and record_fleet_state:
|
||||
|
@ -348,15 +344,23 @@ class Learner:
|
|||
|
||||
return discovered
|
||||
|
||||
def read_nodes_from_storage(self) -> None:
|
||||
def read_nodes_from_storage(self) -> List:
|
||||
stored_nodes = self.node_storage.all(federated_only=self.federated_only) # TODO: #466
|
||||
|
||||
restored_from_disk = []
|
||||
|
||||
invalid_nodes = defaultdict(list)
|
||||
for node in stored_nodes:
|
||||
node_domain = node.domain.decode('utf-8')
|
||||
if node_domain != self.learning_domain:
|
||||
invalid_nodes[node_domain].append(node)
|
||||
continue
|
||||
restored_node = self.remember_node(node, record_fleet_state=False) # TODO: Validity status 1866
|
||||
restored_from_disk.append(restored_node)
|
||||
|
||||
if invalid_nodes:
|
||||
self.log.warn(f"We're learning about domain '{self.learning_domain}', but found nodes from other domains; "
|
||||
f"let's ignore them. These domains and nodes are: {dict(invalid_nodes)}")
|
||||
|
||||
return restored_from_disk
|
||||
|
||||
def remember_node(self,
|
||||
|
@ -393,7 +397,10 @@ class Learner:
|
|||
stranger_certificate = node.certificate
|
||||
|
||||
# Store node's certificate - It has been seen.
|
||||
certificate_filepath = self.node_storage.store_node_certificate(certificate=stranger_certificate)
|
||||
try:
|
||||
certificate_filepath = self.node_storage.store_node_certificate(certificate=stranger_certificate)
|
||||
except InvalidNodeCertificate:
|
||||
return False # that was easy
|
||||
|
||||
# In some cases (seed nodes or other temp stored certs),
|
||||
# this will update the filepath from the temp location to this one.
|
||||
|
@ -818,12 +825,10 @@ class Learner:
|
|||
self.log.info("Bad response from teacher {}: {} - {}".format(current_teacher, response, response.content))
|
||||
return
|
||||
|
||||
if not set(self.learning_domains).intersection(set(current_teacher.serving_domains)):
|
||||
teacher_domains = ",".join(current_teacher.serving_domains)
|
||||
learner_domains = ",".join(self.learning_domains)
|
||||
self.log.debug(
|
||||
f"{current_teacher} is serving {teacher_domains}, but we are learning {learner_domains}")
|
||||
return # This node is not serving any of our domains.
|
||||
if self.learning_domain != current_teacher.serving_domain:
|
||||
self.log.debug(f"{current_teacher} is serving '{current_teacher.serving_domain}', "
|
||||
f"ignore since we are learning about '{self.learning_domain}'")
|
||||
return # This node is not serving our domain.
|
||||
|
||||
#
|
||||
# Deserialize
|
||||
|
@ -933,7 +938,7 @@ class Teacher:
|
|||
__DEFAULT_MIN_SEED_STAKE = 0
|
||||
|
||||
def __init__(self,
|
||||
domains: Set,
|
||||
domain: str, # TODO: Consider using a Domain type
|
||||
certificate: Certificate,
|
||||
certificate_filepath: str,
|
||||
interface_signature=NOT_SIGNED.bool_value(False),
|
||||
|
@ -945,7 +950,7 @@ class Teacher:
|
|||
# Fleet
|
||||
#
|
||||
|
||||
self.serving_domains = domains
|
||||
self.serving_domain = domain
|
||||
self.fleet_state_checksum = None
|
||||
self.fleet_state_updated = None
|
||||
self.last_seen = NEVER_SEEN("No Connection to Node")
|
||||
|
@ -992,9 +997,19 @@ class Teacher:
|
|||
class WrongMode(TypeError):
|
||||
"""Raised when a Character tries to use another Character as decentralized when the latter is federated_only."""
|
||||
|
||||
class IsFromTheFuture(TypeError):
|
||||
class UnexpectedVersion(TypeError):
|
||||
"""Raised when deserializing a Character from a unexpected and incompatible version."""
|
||||
|
||||
class IsFromTheFuture(UnexpectedVersion):
|
||||
"""Raised when deserializing a Character from a future version."""
|
||||
|
||||
class AreYouFromThePast(UnexpectedVersion):
|
||||
"""Raised when deserializing a Character from a previous, now unsupported version."""
|
||||
|
||||
unknown_version_message = "{} purported to be of version {}, but we're version {}."
|
||||
really_unknown_version_message = "Unable to glean address from node that purported to be version {}. " \
|
||||
"We're version {}."
|
||||
|
||||
@classmethod
|
||||
def set_cert_storage_function(cls, node_storage_function):
|
||||
cls._cert_store_function = node_storage_function
|
||||
|
@ -1216,7 +1231,7 @@ class Teacher:
|
|||
|
||||
version, node_bytes = self.version_splitter(response_data, return_remainder=True)
|
||||
|
||||
sprout = self.internal_splitter(node_bytes, partial=True)
|
||||
sprout = self.payload_splitter(node_bytes, partial=True)
|
||||
|
||||
verifying_keys_match = sprout.verifying_key == self.public_keys(SigningPower)
|
||||
encrypting_keys_match = sprout.encrypting_key == self.public_keys(DecryptingPower)
|
||||
|
|
|
@ -23,7 +23,6 @@ from bytestring_splitter import BytestringSplitter
|
|||
from constant_sorrow import constants
|
||||
from constant_sorrow.constants import FLEET_STATES_MATCH, NO_BLOCKCHAIN_CONNECTION, NO_KNOWN_NODES
|
||||
from flask import Flask, Response, jsonify, request
|
||||
from hendrix.experience import crosstown_traffic
|
||||
from jinja2 import Template, TemplateError
|
||||
from typing import Tuple, Set
|
||||
from umbral.keys import UmbralPublicKey
|
||||
|
@ -31,14 +30,15 @@ from umbral.kfrags import KFrag
|
|||
from web3.exceptions import TimeExhausted
|
||||
|
||||
import nucypher
|
||||
from nucypher.crypto.api import InvalidNodeCertificate
|
||||
from nucypher.config.constants import MAX_UPLOAD_CONTENT_LENGTH
|
||||
from nucypher.config.storages import ForgetfulNodeStorage
|
||||
from nucypher.crypto.keypairs import HostingKeypair
|
||||
from nucypher.crypto.kits import UmbralMessageKit
|
||||
from nucypher.crypto.powers import KeyPairBasedPower, PowerUpError
|
||||
from nucypher.crypto.signing import InvalidSignature
|
||||
from nucypher.crypto.utils import canonical_address_from_umbral_key
|
||||
from nucypher.datastore.datastore import Datastore, RecordNotFound, DatastoreTransactionError
|
||||
from nucypher.datastore.keypairs import HostingKeypair
|
||||
from nucypher.datastore.models import PolicyArrangement, Workorder
|
||||
from nucypher.network import LEARNING_LOOP_VERSION
|
||||
from nucypher.network.exceptions import NodeSeemsToBeDown
|
||||
|
@ -81,7 +81,7 @@ class ProxyRESTServer:
|
|||
def make_rest_app(
|
||||
db_filepath: str,
|
||||
this_node,
|
||||
serving_domains: Set[str],
|
||||
serving_domain,
|
||||
log: Logger=Logger("http-application-layer")
|
||||
) -> Tuple[Flask, Datastore]:
|
||||
"""
|
||||
|
@ -98,14 +98,14 @@ def make_rest_app(
|
|||
|
||||
log.info("Starting datastore {}".format(db_filepath))
|
||||
datastore = Datastore(db_filepath)
|
||||
rest_app = _make_rest_app(weakref.proxy(datastore), weakref.proxy(this_node), serving_domains, log)
|
||||
rest_app = _make_rest_app(weakref.proxy(datastore), weakref.proxy(this_node), serving_domain, log)
|
||||
|
||||
return rest_app, datastore
|
||||
|
||||
|
||||
def _make_rest_app(datastore: Datastore, this_node, serving_domains: Set[str], log: Logger) -> Tuple[Flask, Datastore]:
|
||||
def _make_rest_app(datastore: Datastore, this_node, serving_domain: str, log: Logger) -> Tuple[Flask, Datastore]:
|
||||
|
||||
forgetful_node_storage = ForgetfulNodeStorage(federated_only=this_node.federated_only)
|
||||
forgetful_node_storage = ForgetfulNodeStorage(federated_only=this_node.federated_only) # FIXME: Seems unused
|
||||
|
||||
from nucypher.characters.lawful import Alice, Ursula
|
||||
_alice_class = Alice
|
||||
|
@ -158,6 +158,9 @@ def _make_rest_app(datastore: Datastore, this_node, serving_domains: Set[str], l
|
|||
except NodeSeemsToBeDown:
|
||||
return Response({'error': 'Unreachable node'}, status=400) # ... toasted
|
||||
|
||||
except InvalidNodeCertificate:
|
||||
return Response({'error': 'Invalid TLS certificate - missing checksum address'}, status=400) # ... invalid
|
||||
|
||||
# Compare the results of the outer POST with the inner GET... yum
|
||||
if requesting_ursula_bytes == request.data:
|
||||
return Response(status=200)
|
||||
|
@ -414,7 +417,7 @@ def _make_rest_app(datastore: Datastore, this_node, serving_domains: Set[str], l
|
|||
content = status_template.render(this_node=this_node,
|
||||
known_nodes=this_node.known_nodes,
|
||||
previous_states=previous_states,
|
||||
domains=serving_domains,
|
||||
domain=serving_domain,
|
||||
version=nucypher.__version__,
|
||||
checksum_address=this_node.checksum_address)
|
||||
except Exception as e:
|
||||
|
|
|
@ -22,6 +22,7 @@ from twisted.internet import reactor
|
|||
from twisted.internet.task import LoopingCall
|
||||
from typing import Union
|
||||
|
||||
from nucypher.crypto.api import InvalidNodeCertificate
|
||||
from nucypher.network.exceptions import NodeSeemsToBeDown
|
||||
from nucypher.network.middleware import RestMiddleware
|
||||
from nucypher.network.nodes import NodeSprout
|
||||
|
@ -217,7 +218,7 @@ class AvailabilityTracker:
|
|||
# TODO: Relocate?
|
||||
Unreachable = (*NodeSeemsToBeDown,
|
||||
self._ursula.NotStaking,
|
||||
self._ursula.node_storage.InvalidNodeCertificate,
|
||||
InvalidNodeCertificate,
|
||||
self._ursula.network_middleware.UnexpectedResponse)
|
||||
|
||||
if not ursulas:
|
||||
|
|
|
@ -24,15 +24,18 @@ from cryptography.hazmat.backends.openssl import backend
|
|||
from cryptography.hazmat.primitives import hashes
|
||||
from eth_utils import to_canonical_address, to_checksum_address
|
||||
|
||||
from bytestring_splitter import BytestringKwargifier
|
||||
from bytestring_splitter import BytestringSplitter, BytestringSplittingError, VariableLengthBytestring
|
||||
from bytestring_splitter import (
|
||||
BytestringKwargifier,
|
||||
BytestringSplitter,
|
||||
BytestringSplittingError,
|
||||
VariableLengthBytestring
|
||||
)
|
||||
from constant_sorrow.constants import CFRAG_NOT_RETAINED, NO_DECRYPTION_PERFORMED
|
||||
from constant_sorrow.constants import NOT_SIGNED
|
||||
from nucypher.blockchain.eth.constants import ETH_ADDRESS_BYTE_LENGTH, ETH_HASH_BYTE_LENGTH
|
||||
from nucypher.characters.lawful import Bob, Character
|
||||
from nucypher.crypto.api import encrypt_and_sign, keccak_digest
|
||||
from nucypher.crypto.api import verify_eip_191
|
||||
from nucypher.crypto.constants import KECCAK_DIGEST_LENGTH, PUBLIC_ADDRESS_LENGTH
|
||||
from nucypher.crypto.api import encrypt_and_sign, keccak_digest, verify_eip_191
|
||||
from nucypher.crypto.constants import KECCAK_DIGEST_LENGTH
|
||||
from nucypher.crypto.kits import UmbralMessageKit
|
||||
from nucypher.crypto.signing import InvalidSignature, Signature, signature_splitter
|
||||
from nucypher.crypto.splitters import capsule_splitter, cfrag_splitter, key_splitter
|
||||
|
@ -60,7 +63,7 @@ class TreasureMap:
|
|||
leaves Bob disoriented.
|
||||
"""
|
||||
|
||||
node_id_splitter = BytestringSplitter((to_checksum_address, int(PUBLIC_ADDRESS_LENGTH)), ID_LENGTH)
|
||||
node_id_splitter = BytestringSplitter((to_checksum_address, ETH_ADDRESS_BYTE_LENGTH), ID_LENGTH)
|
||||
|
||||
from nucypher.crypto.signing import \
|
||||
InvalidSignature # Raised when the public signature (typically intended for Ursula) is not valid.
|
||||
|
|
|
@ -117,7 +117,7 @@ class UrsulaInfoMetricsCollector(BaseMetricsCollector):
|
|||
base_payload = {'app_version': nucypher.__version__,
|
||||
'teacher_version': str(self.ursula.TEACHER_VERSION),
|
||||
'host': str(self.ursula.rest_interface),
|
||||
'domains': str(', '.join(self.ursula.learning_domains)),
|
||||
'domain': self.ursula.learning_domain,
|
||||
'fleet_state': str(self.ursula.known_nodes.checksum),
|
||||
'known_nodes': str(len(self.ursula.known_nodes))
|
||||
}
|
||||
|
|
|
@ -23,54 +23,48 @@ import os
|
|||
|
||||
from typing import Set, Optional, Dict, List
|
||||
|
||||
from nucypher.blockchain.eth.registry import BaseContractRegistry
|
||||
from nucypher.cli.literature import (
|
||||
START_LOADING_SEEDNODES,
|
||||
NO_DOMAIN_PEERS,
|
||||
UNREADABLE_SEEDNODE_ADVISORY,
|
||||
SEEDNODE_NOT_STAKING_WARNING
|
||||
)
|
||||
from nucypher.config.constants import DEFAULT_CONFIG_ROOT
|
||||
from nucypher.network.exceptions import NodeSeemsToBeDown
|
||||
from nucypher.network.middleware import RestMiddleware
|
||||
|
||||
|
||||
def load_static_nodes(domains: Set[str], filepath: Optional[str] = None) -> Dict[str, 'Ursula']:
|
||||
"""
|
||||
Non-invasive read teacher-uris from a JSON configuration file keyed by domain name.
|
||||
and return a filtered subset of domains and teacher URIs as a dict.
|
||||
"""
|
||||
# TODO: This module seems unused
|
||||
|
||||
if not filepath:
|
||||
filepath = os.path.join(DEFAULT_CONFIG_ROOT, 'static-nodes.json')
|
||||
try:
|
||||
with open(filepath, 'r') as file:
|
||||
static_nodes = json.load(file)
|
||||
except FileNotFoundError:
|
||||
return dict() # No static nodes file, No static nodes.
|
||||
except JSONDecodeError:
|
||||
raise RuntimeError(f"Static nodes file '{filepath}' contains invalid JSON.")
|
||||
filtered_static_nodes = {domain: uris for domain, uris in static_nodes.items() if domain in domains}
|
||||
return filtered_static_nodes
|
||||
|
||||
|
||||
def aggregate_seednode_uris(domains: set, highest_priority: Optional[List[str]] = None) -> List[str]:
|
||||
|
||||
# Read from the disk
|
||||
static_nodes = load_static_nodes(domains=domains)
|
||||
|
||||
# Priority 1 - URI passed via --teacher
|
||||
uris = highest_priority or list()
|
||||
for domain in domains:
|
||||
|
||||
# 2 - Static nodes from JSON file
|
||||
domain_static_nodes = static_nodes.get(domain)
|
||||
if domain_static_nodes:
|
||||
uris.extend(domain_static_nodes)
|
||||
|
||||
# 3 - Hardcoded teachers from module
|
||||
hardcoded_uris = TEACHER_NODES.get(domain)
|
||||
if hardcoded_uris:
|
||||
uris.extend(hardcoded_uris)
|
||||
|
||||
return uris
|
||||
# def load_static_nodes(domains: Set[str], filepath: Optional[str] = None) -> Dict[str, 'Ursula']:
|
||||
# """
|
||||
# Non-invasive read teacher-uris from a JSON configuration file keyed by domain name.
|
||||
# and return a filtered subset of domains and teacher URIs as a dict.
|
||||
# """
|
||||
#
|
||||
# if not filepath:
|
||||
# filepath = os.path.join(DEFAULT_CONFIG_ROOT, 'static-nodes.json')
|
||||
# try:
|
||||
# with open(filepath, 'r') as file:
|
||||
# static_nodes = json.load(file)
|
||||
# except FileNotFoundError:
|
||||
# return dict() # No static nodes file, No static nodes.
|
||||
# except JSONDecodeError:
|
||||
# raise RuntimeError(f"Static nodes file '{filepath}' contains invalid JSON.")
|
||||
# filtered_static_nodes = {domain: uris for domain, uris in static_nodes.items() if domain in domains}
|
||||
# return filtered_static_nodes
|
||||
#
|
||||
#
|
||||
#
|
||||
# def aggregate_seednode_uris(domains: set, highest_priority: Optional[List[str]] = None) -> List[str]:
|
||||
#
|
||||
# # Read from the disk
|
||||
# static_nodes = load_static_nodes(domains=domains)
|
||||
#
|
||||
# # Priority 1 - URI passed via --teacher
|
||||
# uris = highest_priority or list()
|
||||
# for domain in domains:
|
||||
#
|
||||
# # 2 - Static nodes from JSON file
|
||||
# domain_static_nodes = static_nodes.get(domain)
|
||||
# if domain_static_nodes:
|
||||
# uris.extend(domain_static_nodes)
|
||||
#
|
||||
# # 3 - Hardcoded teachers from module
|
||||
# hardcoded_uris = TEACHER_NODES.get(domain)
|
||||
# if hardcoded_uris:
|
||||
# uris.extend(hardcoded_uris)
|
||||
#
|
||||
# return uris
|
||||
|
|
|
@ -146,7 +146,7 @@ def test_alice_view_preexisting_configuration(click_runner, custom_filepath):
|
|||
result = click_runner.invoke(nucypher_cli, view_args, input=FAKE_PASSWORD_CONFIRMED)
|
||||
assert result.exit_code == 0
|
||||
assert "checksum_address" in result.output
|
||||
assert "domains" in result.output
|
||||
assert "domain" in result.output
|
||||
assert TEMPORARY_DOMAIN in result.output
|
||||
assert str(custom_filepath) in result.output
|
||||
|
||||
|
|
|
@ -100,7 +100,7 @@ def test_bob_view_with_preexisting_configuration(click_runner, custom_filepath):
|
|||
result = click_runner.invoke(nucypher_cli, view_args, input=FAKE_PASSWORD_CONFIRMED)
|
||||
assert result.exit_code == 0, result.exception
|
||||
assert "checksum_address" in result.output
|
||||
assert "domains" in result.output
|
||||
assert "domain" in result.output
|
||||
assert TEMPORARY_DOMAIN in result.output
|
||||
assert str(custom_filepath) in result.output
|
||||
|
||||
|
|
|
@ -450,7 +450,7 @@ def test_ursula_init(click_runner,
|
|||
config_data = json.loads(raw_config_data)
|
||||
assert config_data['provider_uri'] == TEST_PROVIDER_URI
|
||||
assert config_data['worker_address'] == manual_worker
|
||||
assert TEMPORARY_DOMAIN in config_data['domains']
|
||||
assert TEMPORARY_DOMAIN == config_data['domain']
|
||||
|
||||
|
||||
def test_ursula_run(click_runner,
|
||||
|
|
|
@ -347,7 +347,7 @@ def test_ursula_init(click_runner,
|
|||
config_data = json.loads(raw_config_data)
|
||||
assert config_data['provider_uri'] == TEST_PROVIDER_URI
|
||||
assert config_data['worker_address'] == manual_worker
|
||||
assert TEMPORARY_DOMAIN in config_data['domains']
|
||||
assert TEMPORARY_DOMAIN == config_data['domain']
|
||||
|
||||
|
||||
def test_ursula_run(click_runner,
|
||||
|
|
|
@ -401,9 +401,9 @@ def federated_ursulas(ursula_federated_test_config):
|
|||
def lonely_ursula_maker(ursula_federated_test_config):
|
||||
class _PartialUrsulaMaker:
|
||||
_partial = partial(make_federated_ursulas,
|
||||
ursula_config=ursula_federated_test_config,
|
||||
know_each_other=False,
|
||||
)
|
||||
ursula_config=ursula_federated_test_config,
|
||||
know_each_other=False,
|
||||
)
|
||||
_made = []
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
|
@ -1011,7 +1011,7 @@ def fleet_of_highperf_mocked_ursulas(ursula_federated_test_config, request):
|
|||
@pytest.fixture(scope="module")
|
||||
def highperf_mocked_alice(fleet_of_highperf_mocked_ursulas):
|
||||
config = AliceConfiguration(dev_mode=True,
|
||||
domains={TEMPORARY_DOMAIN},
|
||||
domain=TEMPORARY_DOMAIN,
|
||||
network_middleware=MockRestMiddlewareForLargeFleetTests(),
|
||||
federated_only=True,
|
||||
abort_on_learning_error=True,
|
||||
|
@ -1028,7 +1028,7 @@ def highperf_mocked_alice(fleet_of_highperf_mocked_ursulas):
|
|||
@pytest.fixture(scope="module")
|
||||
def highperf_mocked_bob(fleet_of_highperf_mocked_ursulas):
|
||||
config = BobConfiguration(dev_mode=True,
|
||||
domains={TEMPORARY_DOMAIN},
|
||||
domain=TEMPORARY_DOMAIN,
|
||||
network_middleware=MockRestMiddlewareForLargeFleetTests(),
|
||||
federated_only=True,
|
||||
abort_on_learning_error=True,
|
||||
|
|
|
@ -81,7 +81,7 @@ def test_bob_can_follow_treasure_map_even_if_he_only_knows_of_one_node(enacted_f
|
|||
from nucypher.characters.lawful import Bob
|
||||
|
||||
bob = Bob(network_middleware=MockRestMiddleware(),
|
||||
domains={TEMPORARY_DOMAIN},
|
||||
domain=TEMPORARY_DOMAIN,
|
||||
start_learning_now=False,
|
||||
abort_on_learning_error=True,
|
||||
federated_only=True)
|
||||
|
|
|
@ -67,7 +67,7 @@ def test_bob_joins_policy_and_retrieves(federated_alice,
|
|||
|
||||
# Bob becomes
|
||||
bob = Bob(federated_only=True,
|
||||
domains={TEMPORARY_DOMAIN},
|
||||
domain=TEMPORARY_DOMAIN,
|
||||
start_learning_now=True,
|
||||
network_middleware=MockRestMiddleware(),
|
||||
abort_on_learning_error=True,
|
||||
|
|
|
@ -21,7 +21,7 @@ from tests.utils.ursula import make_federated_ursulas
|
|||
|
||||
|
||||
def test_new_federated_ursula_announces_herself(lonely_ursula_maker):
|
||||
ursula_in_a_house, ursula_with_a_mouse = lonely_ursula_maker(quantity=2, domains=["useless_domain"])
|
||||
ursula_in_a_house, ursula_with_a_mouse = lonely_ursula_maker(quantity=2, domain="useless_domain")
|
||||
|
||||
# Neither Ursula knows about the other.
|
||||
assert ursula_in_a_house.known_nodes == ursula_with_a_mouse.known_nodes
|
||||
|
|
|
@ -37,7 +37,7 @@ def test_select_client_account_for_staking_cli_action(test_emitter,
|
|||
selected_index = 0
|
||||
selected_account = mock_testerchain.client.accounts[selected_index]
|
||||
|
||||
stakeholder = StakeHolder(registry=test_registry, domains={TEMPORARY_DOMAIN})
|
||||
stakeholder = StakeHolder(registry=test_registry, domain=TEMPORARY_DOMAIN)
|
||||
|
||||
client_account, staking_address = select_client_account_for_staking(emitter=test_emitter,
|
||||
stakeholder=stakeholder,
|
||||
|
|
|
@ -134,7 +134,7 @@ def test_stakeholder_configuration(test_emitter, test_registry, mock_testerchain
|
|||
selected_index = 0
|
||||
selected_account = mock_testerchain.client.accounts[selected_index]
|
||||
expected_stakeholder = StakeHolder(registry=test_registry,
|
||||
domains={TEMPORARY_DOMAIN},
|
||||
domain=TEMPORARY_DOMAIN,
|
||||
initial_address=selected_account)
|
||||
expected_stakeholder.refresh_stakes()
|
||||
|
||||
|
|
|
@ -54,11 +54,11 @@ all_configurations = tuple(configurations + blockchain_only_configurations)
|
|||
@pytest.mark.parametrize("character,configuration", characters_and_configurations)
|
||||
def test_federated_development_character_configurations(character, configuration):
|
||||
|
||||
config = configuration(dev_mode=True, federated_only=True, lonely=True, domains={TEMPORARY_DOMAIN})
|
||||
config = configuration(dev_mode=True, federated_only=True, lonely=True, domain=TEMPORARY_DOMAIN)
|
||||
assert config.is_me is True
|
||||
assert config.dev_mode is True
|
||||
assert config.keyring == NO_KEYRING_ATTACHED
|
||||
assert config.provider_uri == None
|
||||
assert config.provider_uri is None
|
||||
|
||||
# Production
|
||||
thing_one = config()
|
||||
|
@ -76,9 +76,8 @@ def test_federated_development_character_configurations(character, configuration
|
|||
# Operating Mode
|
||||
assert thing_one.federated_only is True
|
||||
|
||||
# Domains
|
||||
domains = thing_one.learning_domains
|
||||
assert domains == [TEMPORARY_DOMAIN]
|
||||
# Domain
|
||||
assert TEMPORARY_DOMAIN == thing_one.learning_domain
|
||||
|
||||
# Node Storage
|
||||
assert configuration.TEMP_CONFIGURATION_DIR_PREFIX in thing_one.keyring_root
|
||||
|
@ -97,6 +96,7 @@ def test_federated_development_character_configurations(character, configuration
|
|||
alice.disenchant()
|
||||
|
||||
|
||||
# TODO: This test is unnecessarily slow due to the blockchain configurations. Perhaps we should mock them -- See #2230
|
||||
@pytest.mark.parametrize('configuration_class', all_configurations)
|
||||
def test_default_character_configuration_preservation(configuration_class, testerchain, test_registry_source_manager):
|
||||
|
||||
|
@ -115,9 +115,9 @@ def test_default_character_configuration_preservation(configuration_class, teste
|
|||
|
||||
if configuration_class == StakeHolderConfiguration:
|
||||
# special case for defaults
|
||||
character_config = StakeHolderConfiguration(provider_uri=testerchain.provider_uri, domains={network})
|
||||
character_config = StakeHolderConfiguration(provider_uri=testerchain.provider_uri, domain=network)
|
||||
else:
|
||||
character_config = configuration_class(checksum_address=fake_address, domains={network})
|
||||
character_config = configuration_class(checksum_address=fake_address, domain=network)
|
||||
|
||||
generated_filepath = character_config.generate_filepath()
|
||||
assert generated_filepath == expected_filepath
|
||||
|
|
|
@ -15,15 +15,18 @@ You should have received a copy of the GNU Affero General Public License
|
|||
along with nucypher. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
import os
|
||||
import pytest
|
||||
|
||||
from nucypher.characters.lawful import Ursula
|
||||
from nucypher.config.storages import (ForgetfulNodeStorage, NodeStorage,
|
||||
TemporaryFileBasedNodeStorage)
|
||||
from tests.constants import (
|
||||
MOCK_URSULA_DB_FILEPATH)
|
||||
from nucypher.config.storages import ForgetfulNodeStorage, NodeStorage, TemporaryFileBasedNodeStorage
|
||||
from nucypher.network.nodes import Learner
|
||||
|
||||
from tests.constants import MOCK_URSULA_DB_FILEPATH
|
||||
from tests.utils.ursula import MOCK_URSULA_STARTING_PORT
|
||||
|
||||
ADDITIONAL_NODES_TO_LEARN_ABOUT = 10
|
||||
|
||||
|
||||
class BaseTestNodeStorageBackends:
|
||||
|
||||
|
@ -50,7 +53,7 @@ class BaseTestNodeStorageBackends:
|
|||
|
||||
# Save more nodes
|
||||
all_known_nodes = set()
|
||||
for port in range(MOCK_URSULA_STARTING_PORT, MOCK_URSULA_STARTING_PORT+100):
|
||||
for port in range(MOCK_URSULA_STARTING_PORT, MOCK_URSULA_STARTING_PORT + ADDITIONAL_NODES_TO_LEARN_ABOUT):
|
||||
node = Ursula(rest_host='127.0.0.1', db_filepath=MOCK_URSULA_DB_FILEPATH, rest_port=port,
|
||||
federated_only=True)
|
||||
node_storage.store_node_metadata(node=node)
|
||||
|
@ -59,10 +62,10 @@ class BaseTestNodeStorageBackends:
|
|||
# Read all nodes from storage
|
||||
all_stored_nodes = node_storage.all(federated_only=True)
|
||||
all_known_nodes.add(ursula)
|
||||
assert len(all_known_nodes) == len(all_stored_nodes)
|
||||
assert len(all_known_nodes) == len(all_stored_nodes) == 1 + ADDITIONAL_NODES_TO_LEARN_ABOUT
|
||||
|
||||
known_checksums = sorted([n.checksum_address for n in all_known_nodes])
|
||||
stored_checksums = sorted([n.checksum_address for n in all_stored_nodes])
|
||||
known_checksums = sorted(n.checksum_address for n in all_known_nodes)
|
||||
stored_checksums = sorted(n.checksum_address for n in all_stored_nodes)
|
||||
|
||||
assert known_checksums == stored_checksums
|
||||
|
||||
|
@ -100,6 +103,7 @@ class BaseTestNodeStorageBackends:
|
|||
|
||||
def test_read_and_write_to_storage(self, light_ursula):
|
||||
assert self._read_and_write_metadata(ursula=light_ursula, node_storage=self.storage_backend)
|
||||
self.storage_backend.clear()
|
||||
|
||||
|
||||
class TestInMemoryNodeStorage(BaseTestNodeStorageBackends):
|
||||
|
@ -112,3 +116,32 @@ class TestTemporaryFileBasedNodeStorage(BaseTestNodeStorageBackends):
|
|||
storage_backend = TemporaryFileBasedNodeStorage(character_class=BaseTestNodeStorageBackends.character_class,
|
||||
federated_only=BaseTestNodeStorageBackends.federated_only)
|
||||
storage_backend.initialize()
|
||||
|
||||
def test_invalid_metadata(self, light_ursula):
|
||||
self._read_and_write_metadata(ursula=light_ursula, node_storage=self.storage_backend)
|
||||
some_node, another_node, *other = os.listdir(self.storage_backend.metadata_dir)
|
||||
|
||||
# Let's break the metadata (but not the version)
|
||||
metadata_path = os.path.join(self.storage_backend.metadata_dir, some_node)
|
||||
with open(metadata_path, 'wb') as file:
|
||||
file.write(Learner.LEARNER_VERSION.to_bytes(4, 'big') + b'invalid')
|
||||
|
||||
with pytest.raises(TemporaryFileBasedNodeStorage.InvalidNodeMetadata):
|
||||
self.storage_backend.get(checksum_address=some_node[:-5],
|
||||
federated_only=True,
|
||||
certificate_only=False)
|
||||
|
||||
# Let's break the metadata, by putting a completely wrong version
|
||||
metadata_path = os.path.join(self.storage_backend.metadata_dir, another_node)
|
||||
with open(metadata_path, 'wb') as file:
|
||||
file.write(b'meh') # Versions are expected to be 4 bytes, but this is 3 bytes
|
||||
|
||||
with pytest.raises(TemporaryFileBasedNodeStorage.InvalidNodeMetadata):
|
||||
self.storage_backend.get(checksum_address=another_node[:-5],
|
||||
federated_only=True,
|
||||
certificate_only=False)
|
||||
|
||||
# Since there are 2 broken metadata files, we should get 2 nodes less when reading all
|
||||
restored_nodes = self.storage_backend.all(federated_only=True, certificates_only=False)
|
||||
total_nodes = 1 + ADDITIONAL_NODES_TO_LEARN_ABOUT
|
||||
assert total_nodes - 2 == len(restored_nodes)
|
||||
|
|
|
@ -189,7 +189,7 @@ def test_mass_treasure_map_placement(fleet_of_highperf_mocked_ursulas,
|
|||
|
||||
# The number of nodes having the map is approximately the number you'd expect from full utilization of Alice's publication threadpool.
|
||||
# TODO: This line fails sometimes because the loop goes too fast.
|
||||
assert len(nodes_that_have_the_map_when_we_unblock) == pytest.approx(policy.publishing_mutex._block_until_this_many_are_complete, .2)
|
||||
# assert len(nodes_that_have_the_map_when_we_unblock) == pytest.approx(policy.publishing_mutex._block_until_this_many_are_complete, .2)
|
||||
|
||||
# PART III: Having made proper assertions about the publication call and the first block, we allow the rest to
|
||||
# happen in the background and then ensure that each phase was timely.
|
||||
|
|
|
@ -17,39 +17,78 @@
|
|||
|
||||
from functools import partial
|
||||
|
||||
from tests.utils.ursula import make_federated_ursulas
|
||||
from nucypher.acumen.perception import FleetSensor
|
||||
from nucypher.config.storages import LocalFileBasedNodeStorage
|
||||
|
||||
|
||||
def test_learner_learns_about_domains_separately(lonely_ursula_maker, caplog):
|
||||
_lonely_ursula_maker = partial(lonely_ursula_maker, know_each_other=True, quantity=3)
|
||||
_lonely_ursula_maker = partial(lonely_ursula_maker, know_each_other=True, quantity=3)
|
||||
|
||||
global_learners = _lonely_ursula_maker(domains={"nucypher1.test_suite"})
|
||||
first_domain_learners = _lonely_ursula_maker(domains={"nucypher1.test_suite"})
|
||||
second_domain_learners = _lonely_ursula_maker(domains={"nucypher2.test_suite"})
|
||||
global_learners = _lonely_ursula_maker(domain="nucypher1.test_suite")
|
||||
first_domain_learners = _lonely_ursula_maker(domain="nucypher1.test_suite")
|
||||
second_domain_learners = _lonely_ursula_maker(domain="nucypher2.test_suite")
|
||||
|
||||
big_learner = global_learners.pop()
|
||||
big_learner = global_learners.pop()
|
||||
|
||||
assert len(big_learner.known_nodes) == 2
|
||||
assert len(big_learner.known_nodes) == 2
|
||||
|
||||
# Learn about the fist domain.
|
||||
big_learner._current_teacher_node = first_domain_learners.pop()
|
||||
big_learner.learn_from_teacher_node()
|
||||
# Learn about the fist domain.
|
||||
big_learner._current_teacher_node = first_domain_learners.pop()
|
||||
big_learner.learn_from_teacher_node()
|
||||
|
||||
# Learn about the second domain.
|
||||
big_learner._current_teacher_node = second_domain_learners.pop()
|
||||
big_learner.learn_from_teacher_node()
|
||||
# Learn about the second domain.
|
||||
big_learner._current_teacher_node = second_domain_learners.pop()
|
||||
big_learner.learn_from_teacher_node()
|
||||
|
||||
# All domain 1 nodes
|
||||
assert len(big_learner.known_nodes) == 5
|
||||
# All domain 1 nodes
|
||||
assert len(big_learner.known_nodes) == 5
|
||||
|
||||
new_first_domain_learner = _lonely_ursula_maker(domains={"nucypher1.test_suite"}).pop()
|
||||
_new_second_domain_learner = _lonely_ursula_maker(domains={"nucypher2.test_suite"})
|
||||
new_first_domain_learner = _lonely_ursula_maker(domain="nucypher1.test_suite").pop()
|
||||
_new_second_domain_learner = _lonely_ursula_maker(domain="nucypher2.test_suite")
|
||||
|
||||
new_first_domain_learner._current_teacher_node = big_learner
|
||||
new_first_domain_learner.learn_from_teacher_node()
|
||||
new_first_domain_learner._current_teacher_node = big_learner
|
||||
new_first_domain_learner.learn_from_teacher_node()
|
||||
|
||||
# This node, in the first domain, didn't learn about the second domain.
|
||||
assert not set(second_domain_learners).intersection(set(new_first_domain_learner.known_nodes))
|
||||
# This node, in the first domain, didn't learn about the second domain.
|
||||
assert not set(second_domain_learners).intersection(new_first_domain_learner.known_nodes)
|
||||
|
||||
# However, it learned about *all* of the nodes in its own domain.
|
||||
assert set(first_domain_learners).intersection(set(n.mature() for n in new_first_domain_learner.known_nodes)) == first_domain_learners
|
||||
# However, it learned about *all* of the nodes in its own domain.
|
||||
assert set(first_domain_learners).intersection(
|
||||
n.mature() for n in new_first_domain_learner.known_nodes) == first_domain_learners
|
||||
|
||||
|
||||
def test_learner_restores_metadata_from_storage(lonely_ursula_maker, tmpdir):
|
||||
|
||||
# Create a local file-based node storage
|
||||
root = tmpdir.mkdir("known_nodes")
|
||||
metadata = root.mkdir("metadata")
|
||||
certs = root.mkdir("certs")
|
||||
old_storage = LocalFileBasedNodeStorage(federated_only=True,
|
||||
metadata_dir=metadata,
|
||||
certificates_dir=certs,
|
||||
storage_root=root)
|
||||
|
||||
# Use the ursula maker with this storage so it's populated with nodes from one domain
|
||||
_some_ursulas = lonely_ursula_maker(domain="fistro",
|
||||
node_storage=old_storage,
|
||||
know_each_other=True,
|
||||
quantity=3,
|
||||
save_metadata=True)
|
||||
|
||||
# Create a pair of new learners in a different domain, using the previous storage, and learn from it
|
||||
new_learners = lonely_ursula_maker(domain="duodenal",
|
||||
node_storage=old_storage,
|
||||
quantity=2,
|
||||
know_each_other=True,
|
||||
save_metadata=False)
|
||||
learner, buddy = new_learners
|
||||
buddy._Learner__known_nodes = FleetSensor()
|
||||
|
||||
# The learner shouldn't learn about any node from the first domain, since it's different.
|
||||
learner.learn_from_teacher_node()
|
||||
for restored_node in learner.known_nodes:
|
||||
assert restored_node.mature().serving_domain == learner.learning_domain
|
||||
|
||||
# In fact, since the storage only contains nodes from a different domain,
|
||||
# the learner should only know its buddy from the second domain.
|
||||
assert set(learner.known_nodes) == {buddy}
|
||||
|
|
|
@ -27,7 +27,7 @@ def test_proper_seed_node_instantiation(lonely_ursula_maker):
|
|||
_lonely_ursula_maker = partial(lonely_ursula_maker, quantity=1)
|
||||
firstula = _lonely_ursula_maker().pop()
|
||||
firstula_as_seed_node = firstula.seed_node_metadata()
|
||||
any_other_ursula = _lonely_ursula_maker(seed_nodes=[firstula_as_seed_node], domains=["useless domain"]).pop()
|
||||
any_other_ursula = _lonely_ursula_maker(seed_nodes=[firstula_as_seed_node], domain="useless domain").pop()
|
||||
|
||||
assert not any_other_ursula.known_nodes
|
||||
# print(f"**********************Starting {any_other_ursula} loop")
|
||||
|
|
|
@ -29,11 +29,11 @@ from tests.utils.middleware import MockRestMiddleware
|
|||
|
||||
def test_emit_warning_upon_new_version(lonely_ursula_maker, caplog):
|
||||
seed_node, teacher, new_node = lonely_ursula_maker(quantity=3,
|
||||
domains={"no hardcodes"},
|
||||
domain="no hardcodes",
|
||||
know_each_other=True)
|
||||
learner, _bystander = lonely_ursula_maker(quantity=2, domains={"no hardcodes"})
|
||||
learner, _bystander = lonely_ursula_maker(quantity=2, domain="no hardcodes")
|
||||
|
||||
learner.learning_domains = {"no hardcodes"}
|
||||
learner.learning_domain = "no hardcodes"
|
||||
learner.remember_node(teacher)
|
||||
teacher.remember_node(learner)
|
||||
teacher.remember_node(new_node)
|
||||
|
@ -51,17 +51,16 @@ def test_emit_warning_upon_new_version(lonely_ursula_maker, caplog):
|
|||
# First we'll get a warning, because we're loading a seednode with a version from the future.
|
||||
learner.load_seednodes()
|
||||
assert len(warnings) == 1
|
||||
assert warnings[0]['log_format'] == learner.unknown_version_message.format(seed_node,
|
||||
seed_node.TEACHER_VERSION,
|
||||
learner.LEARNER_VERSION)
|
||||
expected_message = learner.unknown_version_message.format(seed_node,
|
||||
seed_node.TEACHER_VERSION,
|
||||
learner.LEARNER_VERSION)
|
||||
assert expected_message in warnings[0]['log_format']
|
||||
|
||||
# We don't use the above seednode as a teacher, but when our teacher tries to tell us about it, we get another of the same warning.
|
||||
learner.learn_from_teacher_node()
|
||||
|
||||
assert len(warnings) == 2
|
||||
assert warnings[1]['log_format'] == learner.unknown_version_message.format(seed_node,
|
||||
seed_node.TEACHER_VERSION,
|
||||
learner.LEARNER_VERSION)
|
||||
assert expected_message in warnings[1]['log_format']
|
||||
|
||||
# Now let's go a little further: make the version totally unrecognizable.
|
||||
|
||||
|
@ -86,9 +85,10 @@ def test_emit_warning_upon_new_version(lonely_ursula_maker, caplog):
|
|||
accidental_node_repr = Character._display_name_template.format("Ursula", accidental_nickname, accidental_checksum)
|
||||
|
||||
assert len(warnings) == 3
|
||||
assert warnings[2]['log_format'] == learner.unknown_version_message.format(accidental_node_repr,
|
||||
future_version,
|
||||
learner.LEARNER_VERSION)
|
||||
expected_message = learner.unknown_version_message.format(accidental_node_repr,
|
||||
future_version,
|
||||
learner.LEARNER_VERSION)
|
||||
assert expected_message in warnings[2]['log_format']
|
||||
|
||||
# This time, however, there's not enough garbage to assume there's a checksum address...
|
||||
random_bytes = os.urandom(2)
|
||||
|
|
|
@ -0,0 +1,104 @@
|
|||
"""
|
||||
This file is part of nucypher.
|
||||
|
||||
nucypher is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
nucypher is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with nucypher. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
|
||||
from constant_sorrow.constants import UNKNOWN_VERSION
|
||||
|
||||
from nucypher.characters.lawful import Ursula
|
||||
from nucypher.config.constants import TEMPORARY_DOMAIN
|
||||
from nucypher.network.nodes import Teacher
|
||||
|
||||
|
||||
# The following hard-coded hex-strings are versioned Ursulas' metadata.
|
||||
# These hex strings were generated using the code in tests.utils.versions.test_print_ursulas_bytes(),
|
||||
# a script in the form of a test, that simply uses the blockchain_ursula fixture.
|
||||
|
||||
ursulas_v1 = (
|
||||
'0001e57bfe9f44b819898f47bf37e5af72a0783e114100000016000000123a74656d706f726172792d646f6d61696e3a5f593d2c83aaa6a200696737d89ad9f746b867c6a04510be21f56225cbe7528f1c48326bd2b70a0dfd5c632cd274d7b6371c76ca886fe98dbd0686ebdf20e90fda6ae9f300000041b5fcdf6998f1f41532e933aa3f507222ca3bf44a54c3d355c6f2538909984b234ce7218ae72239156b39d318d2f0e8735c4d5fe236278e60913305ec362af94b1b032569686b730892f78b06bd8fe8af83307ae6db27d4fd81eaf7cceb70ff15612f02d8486449e324133937c1f9118688cf464855bee69c1e36c36228186c0b9f48f0000002cd2d2d2d2d2d424547494e2043455254494649434154452d2d2d2d2d0a4d4949423554434341577167417749424167495551475559566c4c46726434503035616c79463576746957522f6d6377436759494b6f5a497a6a3045417751770a535445534d424147413155454177774a4d5449334c6a41754d4334784d544d774d5159445651524244436f77654555314e324a4752546c474e4452694f4445350a4f446b34526a5133516b597a4e305531515559334d6d45774e7a677a5a5445784e4445774868634e4d6a41774f5441354d6a417a4e6a41775768634e4d6a45770a4f5441354d6a417a4e6a4177576a424a4d524977454159445651514444416b784d6a63754d4334774c6a45784d7a417842674e564245454d4b6a4234525455330a596b5a464f5559304e4749344d546b344f5468474e446443526a4d3352545642526a6379595441334f444e6c4d5445304d5442324d42414742797147534d34390a41674547425375424241416941324941424c73686a696177584a597237314f656143746b54467144416a72466b73644c4f454b7842795079316d6e56675a2b520a3173526d6f4d377a3743524b4b4f7856444f762b4b447355555a55324b6539497579737870675549537365556242707a674542556d6e6a775a4551554d5244760a35416f4c622b6e59326131657743717572364d544d424577447759445652305242416777426f6345667741414154414b42676771686b6a4f5051514442414e700a4144426d416a45416a4f4c58516a65322b5a754a474845514d3468734370324f69737632537954342b4145466c47622f716c636d7537386c6c356e564737774e0a5969797736696b5a416a4541674e7a67315063514b577838554d4d78674f456378364c4d4c73356b47617176657653537050436d336a47696e445a2b725131340a3777584136536e6576327a630a2d2d2d2d2d454e442043455254494649434154452d2d2d2d2d0a0000000e3132372e302e302e313a0000c79c',
|
||||
'0001d41c057fd1c78805aac12b0a94a405c0461a6fbb00000016000000123a74656d706f726172792d646f6d61696e3a5f593d2c5c35512cf82c363ad0510316a52bc536b7e50bc2bc26d2e7e027e89599df1f68f09fbce21e454f463a60d695400c32020a7e3e1525c36b084c0c5d62d3b4018f00000041037dce12ecc6269d2fd625a2d3f33af08144ed23a16e3c3e83fb5e97c9bb45565b09a3bddf5df1d386eed1aa8d6761b510a1aa89393f723eac53c425f3e39fec1c02d4a211f477e6eda76623846910c5cf360db9cd21e94e1ff03978edb59a6617580248680b0bd97031ef98a26153f2470d5b516fa172a9fc178c8bc5b7fe1566cab0000002cd2d2d2d2d2d424547494e2043455254494649434154452d2d2d2d2d0a4d494942354443434157716741774942416749554f392f69322f3248546754627958744f362b386a36655a5866336f77436759494b6f5a497a6a3045417751770a535445534d424147413155454177774a4d5449334c6a41754d4334784d544d774d5159445651524244436f77654751304d574d774e54646d5a44466a4e7a67340a4d44564251554d784d6b497751546b30595451774e574d774e44597851545a47516d49774868634e4d6a41774f5441354d6a417a4e6a457a5768634e4d6a45770a4f5441354d6a417a4e6a457a576a424a4d524977454159445651514444416b784d6a63754d4334774c6a45784d7a417842674e564245454d4b6a42345a4451780a597a41314e325a6b4d574d334f4467774e554642517a4579516a42424f5452684e444131597a41304e6a46424e6b5a43596a42324d42414742797147534d34390a41674547425375424241416941324941424e744861433952416d722f56686548423236615656732f39327346792f724f43484f51654377766c6b5767726233610a2f467331435a69474a4c6b387549507858377a4344736e3353574c63776948794e39645153596f4e7475537862414a41655859617630747936534775704835550a463277527a65637564566c4e667759302f614d544d424577447759445652305242416777426f6345667741414154414b42676771686b6a4f5051514442414e6f0a4144426c416a4541353479656659377a416d6b797866495751657a6134514f745350614e4730446646494a3544437239624a4b314953635377384a7167454b730a6d3238764b68567a416a4230536e6e58364f764555507a6e7553665a46426e5634323069622b35474f6f794f4e425772757762676578627177554462613344440a443564727879724663616f3d0a2d2d2d2d2d454e442043455254494649434154452d2d2d2d2d0a0000000e3132372e302e302e313a0000c79d',
|
||||
'0001f1f6619b38a98d6de0800f1defc0a6399eb6d30c00000016000000123a74656d706f726172792d646f6d61696e3a5f593d2cdbdd7fa68737d931a51af7c0af7cf2c8a40ed076ce1b07d03c53705725ae5577ee3c4e0c820cfd3603a6b58e9ae1eb82f86ddafa2c666932a6b6d89fff69f58500000041d3847defa8d5a51a7bf4162ced072ed47991f5d337738bb8388e4a98bc6fd69f3f7e5da723705c9a1fdb32f36c4c00b9bf172327c43a5f203c43fe4b86d1df5f1b02fd072b2c3533ca5db8cddfb05692181dec80bc0a4b5ae3fe66b08a84fe3f4b150230b0dabd08d49c1bc0455fb6e96cf30f70669b55534313ca506a473e9e5f1fc0000002cd2d2d2d2d2d424547494e2043455254494649434154452d2d2d2d2d0a4d494942354443434157716741774942416749556231556a3235346667744b325a7173626d5a59523437306c702b4577436759494b6f5a497a6a3045417751770a535445534d424147413155454177774a4d5449334c6a41754d4334784d544d774d5159445651524244436f7765455978526a59324d546c434d7a68424f54686b0a4e6b526c4d4467774d4559785247566d517a42684e6a4d354f5756434e6d517a4d454d774868634e4d6a41774f5441354d6a417a4e6a49325768634e4d6a45770a4f5441354d6a417a4e6a4932576a424a4d524977454159445651514444416b784d6a63754d4334774c6a45784d7a417842674e564245454d4b6a4234526a46470a4e6a59784f55497a4f4545354f475132524755774f444177526a46455a575a444d4745324d7a6b355a5549325a444d77517a42324d42414742797147534d34390a416745474253754242414169413249414243534b47704b49344f4b7a745249376834574c5043544c33744231395a684e463166653370544974464b5472554b700a4a4f635178732f6e544d4f64696e3949716f3967676e566b624e507a7172524f4268653774772f486c524b6c4675727a745755464f703635326976446c3351540a466f784a62557254483330416e644b4f334b4d544d424577447759445652305242416777426f6345667741414154414b42676771686b6a4f5051514442414e6f0a4144426c416a45413835723550492b57447873534e49414b385a2b4862643666364c2f593170456959654e5a6a67673668514f2f302b53734d476a6159612b420a46672f446a374838416a4230704d454f4a56747054747945524970574274694d42634d67362f637356695449767654586c5666672f686f3373587a4d6572627a0a5647746d646a6368726b513d0a2d2d2d2d2d454e442043455254494649434154452d2d2d2d2d0a0000000e3132372e302e302e313a0000c79e',
|
||||
'0001f7edc8fa1ecc32967f827c9043fcae6ba73afa5c00000016000000123a74656d706f726172792d646f6d61696e3a5f593d2cf704a58cc9ac7709e992b28e6ba14b89ce4e5a84904743103a580dd09158d9858246f73a2362999eaebee6309444c1f3fcf288c24e32c6f12e70829b7dbe579500000041f51c88ce693adb33d439c91c23cfc00f03737d7553ac86fd7d3350171212baad4696f83ebdc5ba54c005c55039b2479b88a1d200af718385fdb67d212bbc59641c03170cb65c5d50b0a9f2c93943089b2414f0682face1f1b5174843352ce5951c3f0256201f115d565e42c75783784734e790d07e8ab48963c024a85a2974ef15b09b000002cd2d2d2d2d2d424547494e2043455254494649434154452d2d2d2d2d0a4d494942354443434157716741774942416749554332686a4b4e6163382f6d61416330716578644330373341582b4977436759494b6f5a497a6a3045417751770a535445534d424147413155454177774a4d5449334c6a41754d4334784d544d774d5159445651524244436f77654559335257526a4f455a424d575644597a4d790a4f545933526a67794e304d354d44517a526d4e425a545a695954637a59575a424e574d774868634e4d6a41774f5441354d6a417a4e6a4d355768634e4d6a45770a4f5441354d6a417a4e6a4d35576a424a4d524977454159445651514444416b784d6a63754d4334774c6a45784d7a417842674e564245454d4b6a4234526a64460a5a474d34526b45785a554e6a4d7a49354e6a64474f444933517a6b774e444e475930466c4e6d4a684e7a4e685a6b4531597a42324d42414742797147534d34390a416745474253754242414169413249414245645a6a654842726347644f454976424f43446a4b65704d6f303258526e79304b71562f3862747339516a3667504a0a763334797642466b31626a4d63374648562b47525a617176545a757865366c694d6a57666b51354c6c50763166325647474a792f5130452b4430636c3353442f0a4f32764245324b5432565244792b596e2b714d544d424577447759445652305242416777426f6345667741414154414b42676771686b6a4f5051514442414e6f0a4144426c416a4541776b326d7a6233574668735533766d4268722b4c4768442f6b48494d6c65477342584b796c4363636741784b70336d4e6e4861467369364e0a6c6963475a72584d416a424268727363567a683558307a384a49497468396337584a78556131664d6d6d5a63336b6e4c486f585a7a4b4c366878346e492f4a310a2f71676a6e714e6e76676f3d0a2d2d2d2d2d454e442043455254494649434154452d2d2d2d2d0a0000000e3132372e302e302e313a0000c79f',
|
||||
'00014cceba2d7d2b4fdce4304d3e09a1fea9fbeb152800000016000000123a74656d706f726172792d646f6d61696e3a5f593d2c8dd3177aa48594ad5bd4d289055b64ae990a9b8b70434da92ea1599bf766b600831a759b9c94b860d540b15f71d0c9bbc35953e5c306bb8b8f91da9298432b5c000000411763c36944ce97078383378af2499914a5cadc1404bd25a40a411844649ef9913c719f68c9c557037722288e9fb0e650f3a958a3c1647460e79986bc49d2d0fd1b0332a966aa2a2b5ee449d41ae572dd0bffab7b3c7d7668227115924822116bfdac02259a2a302a032b7e2d953d8c86fc8a6247bb2b81d597791b9c3d90e206d063f8000002cd2d2d2d2d2d424547494e2043455254494649434154452d2d2d2d2d0a4d494942355443434157716741774942416749556458514b3042527a4c65377867746451446c75762f392f6e72536377436759494b6f5a497a6a3045417751770a535445534d424147413155454177774a4d5449334c6a41754d4334784d544d774d5159445651524244436f77654452445132564359544a6b4e305179516a526d0a5a474e464e444d774e47517a5a5441355954466d5a5745355a6d4a46596a45314d6a67774868634e4d6a41774f5441354d6a417a4e6a55795768634e4d6a45770a4f5441354d6a417a4e6a5579576a424a4d524977454159445651514444416b784d6a63754d4334774c6a45784d7a417842674e564245454d4b6a42344e454e440a5a554a684d6d513352444a434e475a6b593055304d7a41305a444e6c4d446c684d575a6c59546c6d596b56694d5455794f4442324d42414742797147534d34390a4167454742537542424141694132494142476d6b4a4867315172706a54333334676f6c3039746179316d792f6e3935792b612f453844656d4f704b51693258580a48796b2f6d3739436b4d574f494f2f38615a6c7463704f464c2b6159734e58634d7a476865785955616c62424e5254514b763661624f736136365853325434580a59757675724b5664394241466e43514d594b4d544d424577447759445652305242416777426f6345667741414154414b42676771686b6a4f5051514442414e700a4144426d416a45416d55456250694d772b5a44427531757558714131554a633373654668524b44736e4d5a39735a77384d4e5332634348655379474e306342780a62645575442b4143416a4541364b612f6944586d395032654762446d774b494c50324e4b4f664d7173724c446a76304c6446543552636a5332502f317658464d0a4d5534386a55766c364957310a2d2d2d2d2d454e442043455254494649434154452d2d2d2d2d0a0000000e3132372e302e302e313a0000c7a0',
|
||||
'00013da8d322cb2435da26e9c9fee670f9fb7fe74e4900000016000000123a74656d706f726172792d646f6d61696e3a5f593d2cbbff43453245b9fb1d58606cedfd18cafef68fd648c324ee9da076ac79839ff7b9de3756df26a055ae9e3ed2637b6638a785572846f133c8df3ce747abf7e5df0000004102edca7e306c31fe06af9f43916e1128eb16ba29415f1d01211f2530c0cb92ce13eb7174ca5fc1aed5ff022bda01b6b2ab2beef9c3a3f424a5b4baf43368178d1b03942781ebb8f470b31f758a4fa1e2c310ebd39140e772fdf16b64d85af8c2a3380304e6a8579f623d3c8600e8f98422be024ad06dee2948bef462121105210a8a76000002cd2d2d2d2d2d424547494e2043455254494649434154452d2d2d2d2d0a4d494942354443434157716741774942416749556664442b5a4966513574624d6559693453784770334c477a72685177436759494b6f5a497a6a3045417751770a535445534d424147413155454177774a4d5449334c6a41754d4334784d544d774d5159445651524244436f7765444e45515468454d7a4979513049794e444d310a5a4545794e6b5535517a6c6d525555324e7a426d4f575a434e305a6c4e7a52464e446b774868634e4d6a41774f5441354d6a417a4e7a41325768634e4d6a45770a4f5441354d6a417a4e7a4132576a424a4d524977454159445651514444416b784d6a63754d4334774c6a45784d7a417842674e564245454d4b6a42344d3052420a4f45517a4d6a4a44516a49304d7a566b5154493252546c444f575a46525459334d4759355a6b4933526d55334e4555304f5442324d42414742797147534d34390a4167454742537542424141694132494142426463477a7a5a786865554c3235576453796d5a6e714f696f572b4c4d5753543338694759526e347370424a574c6d0a6a6d59674d654557747a48364664714658676b4f592f6e546c325449344858494c7243706d3475356664577564585456666f34684966306e4f4f2b775a6a37670a564d3076416d344f343037783877372f4d364d544d424577447759445652305242416777426f6345667741414154414b42676771686b6a4f5051514442414e6f0a4144426c416a45417a6d4d364c4e59735a732f71555a697668444767506870794d754c7431356b39594a2f677847676e7332546431665a43623550456d4a39700a6e426b4355583043416a4131767631426b48534f662f3761635a4a784866556c664152597165536e582f39653338703831326a66514a6b684177687356365a430a6f314c69374a47576d32383d0a2d2d2d2d2d454e442043455254494649434154452d2d2d2d2d0a0000000e3132372e302e302e313a0000c7a1',
|
||||
'0001dbc23ae43a150ff8884b02cea117b22d1c3b979600000016000000123a74656d706f726172792d646f6d61696e3a5f593d2c3a9a1f028dbdd0723bd244bc3e06a3b4d2b254622dc9b5e9469a0f00fedcb0b108bcb9c310aaacb15a6714d6376b87d898bf7f3b8376168dd41e636b6b0664aa000000419413e0ed684060ebdc48cfacfcff64550affa652d89bd90dcba0644228d0f2d03b6397dec6d55ca7e6257fc143d413d8e3a5ac265a9ac22b0c27e00066908c4d1b036633eee409ce433fbd9a504a97f4b3efa5506a0ce1fdc77ca573c579a9b798e30360d5967444efc24fd2f4839a75776071d8a050c1450b00b29a016edb7e1bcbde000002cd2d2d2d2d2d424547494e2043455254494649434154452d2d2d2d2d0a4d494942355443434157716741774942416749555a46386c784d3873524a34784231464d2f64702b3945713735475577436759494b6f5a497a6a3045417751770a535445534d424147413155454177774a4d5449334c6a41754d4334784d544d774d5159445651524244436f7765455269597a497a515555304d3245784e54426d0a5a6a67344f4452434d444a445a5745784d5464694d6a4a454d574d7a596a6b334f5459774868634e4d6a41774f5441354d6a417a4e7a49775768634e4d6a45770a4f5441354d6a417a4e7a4977576a424a4d524977454159445651514444416b784d6a63754d4334774c6a45784d7a417842674e564245454d4b6a423452474a6a0a4d6a4e425254517a595445314d475a6d4f4467344e4549774d6b4e6c595445784e3249794d6b5178597a4e694f5463354e6a42324d42414742797147534d34390a41674547425375424241416941324941424232655646744e785848584b4b433036503630384b4e306666336a5772302f3357646f4875477a5957684f445669390a4437724453425071394f72346276435274784c6579586a4b43694d764c4c493548496e7843714b763163747463735851474b6c6d3733304841494238344a79710a7a47674e57647a534c446c416654776e2b614d544d424577447759445652305242416777426f6345667741414154414b42676771686b6a4f5051514442414e700a4144426d416a4541315366304c616a713044524946735765746d7a487a5a5a396f4c47547a6e39693973306a38676f37447633465230687976596c77383075390a2b744a5965617279416a45412b762b4c2b753841385342564c376e50656166632b2f4b627042612f304831547448774550615772395a4d41364d6d65453631710a572f59586c6138775848462f0a2d2d2d2d2d454e442043455254494649434154452d2d2d2d2d0a0000000e3132372e302e302e313a0000c7a2',
|
||||
'000168e527780872cda0216ba0d8fbd58b67a5d5e35100000016000000123a74656d706f726172792d646f6d61696e3a5f593d2d856c42a3d8ed363eea5b59c5b63b1b9be4a9ef0ccfe391a34ffc0bf387caa4b9cb0f778347a94bfa996edf5adbf943dcc9dce900d2df1c7dd63593a0cf34c61e00000041369704de40d33e0f6d2599757cebbf5f61545fdc4c720e2627cfdb288eba4da55207f08ff2a36e4a78be38888f536f25658a98d7c408ddbc82d61f2082862bea1b03b52e5ae3cfe92a46a12f3123b039d1d81e32343ee7ace499882af233c78277a203c81667f20095849d68637b2c83c4c3473e074d76d1869f7e148b366cd5502c85000002cd2d2d2d2d2d424547494e2043455254494649434154452d2d2d2d2d0a4d494942354443434157716741774942416749555371682f4b6838546742706f5873634b75716d774e79377556345977436759494b6f5a497a6a3045417751770a535445534d424147413155454177774a4d5449334c6a41754d4334784d544d774d5159445651524244436f7765445934525455794e7a63344d4467334d6d4e6b0a595441794d545a435954426b4f475a4352445534596a5933595456454e57557a4e5445774868634e4d6a41774f5441354d6a417a4e7a4d305768634e4d6a45770a4f5441354d6a417a4e7a4d30576a424a4d524977454159445651514444416b784d6a63754d4334774c6a45784d7a417842674e564245454d4b6a42344e6a68460a4e5449334e7a67774f446379593252684d4449784e6b4a684d4751345a6b4a454e5468694e6a64684e5551315a544d314d5442324d42414742797147534d34390a41674547425375424241416941324941424c64622b43334d5a674e39472f6672564c6a3679686e6472713331496a5a45544c5a4f464162622f52536d354d6e570a6b622f6c7264335162586272353238417550626e6579524361473374494e4b492b5977714f54483963785a6b6c33516e38456355384e6b54365035593131384b0a7831383367776c53674642695958366579364d544d424577447759445652305242416777426f6345667741414154414b42676771686b6a4f5051514442414e6f0a4144426c416a4541325246466649475532673146734361446c4f59427a426449313375656144727a565245494c63577647635968543835764658737435374c350a46534b6f72435475416a42726c5a33337669677852764f6f304964345938645142346f425848716c786949422b584f4b7a4a4c735a38534532706a3172584d2f0a477a2b774c5139576144343d0a2d2d2d2d2d454e442043455254494649434154452d2d2d2d2d0a0000000e3132372e302e302e313a0000c7a3',
|
||||
'00015a83529ff76ac5723a87008c4d9b436ad4ca7d2800000016000000123a74656d706f726172792d646f6d61696e3a5f593d2dab01fed200ede90a6cb05fb3ede24a1ac6db11fb5371ef25248862dc4ea49943042df65df723434616887fd83c18933be508abdbd4c9ffba4113f28ada18524b000000416e47ce353aa3f4c02b7b54674b14e537168db185db950e9c9570e93a7e7b6c805691027167700749f17088d679e933ad8a651ab284f3fb947979674d6b248e571b02213cfdf1b442ea500febe9bf7a50eed7bf1956620df47d2626f7c3da8e027f6e025825fa58fcca874e460a586e8e7ebd32518afbdb56014b619eccb1759650c164000002cd2d2d2d2d2d424547494e2043455254494649434154452d2d2d2d2d0a4d494942354443434157716741774942416749554f576a5a7068595869504270356b706a306a585063585a5648664977436759494b6f5a497a6a3045417751770a535445534d424147413155454177774a4d5449334c6a41754d4334784d544d774d5159445651524244436f77654456424f444d314d6a6c6d5a6a633251574d310a4e7a497a515467334d444134597a52454f5549304d7a5a42524452445154646b4d6a67774868634e4d6a41774f5441354d6a417a4e7a51345768634e4d6a45770a4f5441354d6a417a4e7a5134576a424a4d524977454159445651514444416b784d6a63754d4334774c6a45784d7a417842674e564245454d4b6a42344e5545340a4d7a55794f575a6d4e7a5a42597a55334d6a4e424f4463774d44686a4e455135516a517a4e6b46454e454e424e3251794f4442324d42414742797147534d34390a4167454742537542424141694132494142424e595470424a643566346b44346876557a625a4133434d6769362b463262316152374d43576e2b353633546c37710a643773643066456761684364726e5444333452415756652b59496f582b596148507543776e314d4e6146396472596a48315651514f3051715753616375424b500a5972696a55465253315863504c397a6e4a614d544d424577447759445652305242416777426f6345667741414154414b42676771686b6a4f5051514442414e6f0a4144426c416a42716f7a6334554b6c4d5665464a6c2f314a4a6f2b6d354954337032774e5842697749313876663530444b4a777a792f376b6672714f494b70610a333868484a6673434d514444562f62656172346654643245724c4c575643626e786e41384679756b716b4867774650646b4275695a59326f4d36575a78614b780a576b502f38587745545a4d3d0a2d2d2d2d2d454e442043455254494649434154452d2d2d2d2d0a0000000e3132372e302e302e313a0000c7a4',
|
||||
)
|
||||
|
||||
ursulas_v2 = (
|
||||
'0002e57bfe9f44b819898f47bf37e5af72a0783e1141000000123a74656d706f726172792d646f6d61696e3a5f5f4992796a4d3a22d2e68e7215c5f8c6cb9f01ad75723efd13427dec264a7a9d4d5b852c95a38fcdcd38b49cf6d04f384249ea9c3f79e2758c9650f3378e04a8baa72c000000410a2698b95780a8be3fea07a06e1b4d2f66aaf448ace856c3392216907b8c81e041085a9ece33ca2a4b51c2f6fe664a099ef847de9123695950c5c8235b4d6fa51c03358c067554adb682e5d6fbe7ddc40fe68376a1057532ceac81906e5c86dce1a403d3ce591da9a486bd0b21f6fe62b09b4cc1bc7a15d849c539319dd151c2e76cb3000002cd2d2d2d2d2d424547494e2043455254494649434154452d2d2d2d2d0a4d4949423544434341577167417749424167495563514835355a5457774c7a507a4d6c43385050697a4561486a336b77436759494b6f5a497a6a3045417751770a535445534d424147413155454177774a4d5449334c6a41754d4334784d544d774d5159445651524244436f77654555314e324a4752546c474e4452694f4445350a4f446b34526a5133516b597a4e305531515559334d6d45774e7a677a5a5445784e4445774868634e4d6a41774f5445304d5441304e4449775768634e4d6a45770a4f5445304d5441304e444977576a424a4d524977454159445651514444416b784d6a63754d4334774c6a45784d7a417842674e564245454d4b6a4234525455330a596b5a464f5559304e4749344d546b344f5468474e446443526a4d3352545642526a6379595441334f444e6c4d5445304d5442324d42414742797147534d34390a41674547425375424241416941324941424177446e536b557a5750437165313375367539324d67496a2b5351662b6c775543396e304d4f7647536239583041610a34794e6f48656e35464353514c504f69344d385765632b716567615a6737646b6c766f596c4a5557636e6e7856474e38776f4a2f4a33356c795261613964504a0a3451526f4b70612f543738644d75643738364d544d424577447759445652305242416777426f6345667741414154414b42676771686b6a4f5051514442414e6f0a4144426c416a454134705932637169596742446537622f5930414838576d6f46355062455579655a7343714351785330384b5969304f6845393645745a7444570a6f6d4d654f693472416a41786663563437462f74383361304f72306e50514979362b4b4e576758796d583161424b2f7341533164674b6264652f4e2f495748320a494f377631494133326e6b3d0a2d2d2d2d2d454e442043455254494649434154452d2d2d2d2d0a0000000e3132372e302e302e313a0000c79c',
|
||||
'0002d41c057fd1c78805aac12b0a94a405c0461a6fbb000000123a74656d706f726172792d646f6d61696e3a5f5f499207a5b9573b1c867a42bae625bc8bc254e64c283c44e94370973d64429c3acc0146ebe699d0362539a8bdf887d3759c643399e7d22705520d9af116c07bc4065900000041dc5bde4836719e5d366e525770073117d144c7a8778444a26e652d8466664c6d27b25d78f7543138711006ccb8b647a451ac3f81b7369fe6841183704f7d94ab1c03497de53f1fd0d76860c9b32e9a0fb83fa56f078c93d41ffff6df4c90243f02370223c698f4c458353213970a669c917fdad7a885964e28f88a2da665a0881a1256000002cd2d2d2d2d2d424547494e2043455254494649434154452d2d2d2d2d0a4d494942354443434157716741774942416749555777675237543642786b42776671574870762f415546497352537377436759494b6f5a497a6a3045417751770a535445534d424147413155454177774a4d5449334c6a41754d4334784d544d774d5159445651524244436f77654751304d574d774e54646d5a44466a4e7a67340a4d44564251554d784d6b497751546b30595451774e574d774e44597851545a47516d49774868634e4d6a41774f5445304d5441304e4449795768634e4d6a45770a4f5445304d5441304e444979576a424a4d524977454159445651514444416b784d6a63754d4334774c6a45784d7a417842674e564245454d4b6a42345a4451780a597a41314e325a6b4d574d334f4467774e554642517a4579516a42424f5452684e444131597a41304e6a46424e6b5a43596a42324d42414742797147534d34390a416745474253754242414169413249414247615943785669314f61325350427531656f7836756a6f5639414e435a58555a77623479703736633354305072772f0a5a746f46385942506b37522f6a7a636e3267766e6a78596d5a594b51654e536130616e76336d4c537a4e69526256554a4a786f70576330775865716a654161300a45486864416171387a316e4547684c716e364d544d424577447759445652305242416777426f6345667741414154414b42676771686b6a4f5051514442414e6f0a4144426c416a45413858455a386a63536a414d7a4a676d5973304d714b47626a364477362b57616369797449554d6b794d45734e324d7552386e7939535550460a45564d6434613374416a417a655337702b313262326550374a587537312f594f424741457858684130555469442b6737307151544b59424f644d374b4843716f0a67513632636f6474314a383d0a2d2d2d2d2d454e442043455254494649434154452d2d2d2d2d0a0000000e3132372e302e302e313a0000c79d',
|
||||
'0002f1f6619b38a98d6de0800f1defc0a6399eb6d30c000000123a74656d706f726172792d646f6d61696e3a5f5f49921cf668fa4a688689e79d4b0866fd1e08efef8fc0bc04af68170f97ef4aa6360ae2b8c605a9a5ea976dac756641eecaee2e18a672ca60f97fce49faa2616b6e8e00000041c1eeb0b911b85525854b96af35cf15ee5605fe9c581492d23a7822643f55f9a20f862de8f1c151591c76914ec9e9b550c962bf36adf5dfa30b0306fe1061684d1b0263e45d32f730e73aad42661a0446c7d79161fa85c9ffef815269979d29aed11003f983698b483aa3c1dda10ffff0b24a26b982c89529ee1db1729be2a6a522abdd000002cd2d2d2d2d2d424547494e2043455254494649434154452d2d2d2d2d0a4d494942347a4343415771674177494241674955633645504c4f485a50777836743658454752303056625666524a6b77436759494b6f5a497a6a3045417751770a535445534d424147413155454177774a4d5449334c6a41754d4334784d544d774d5159445651524244436f7765455978526a59324d546c434d7a68424f54686b0a4e6b526c4d4467774d4559785247566d517a42684e6a4d354f5756434e6d517a4d454d774868634e4d6a41774f5445304d5441304e44497a5768634e4d6a45770a4f5445304d5441304e44497a576a424a4d524977454159445651514444416b784d6a63754d4334774c6a45784d7a417842674e564245454d4b6a4234526a46470a4e6a59784f55497a4f4545354f475132524755774f444177526a46455a575a444d4745324d7a6b355a5549325a444d77517a42324d42414742797147534d34390a41674547425375424241416941324941424f586d797162324e7476763541796b745177422b3978584f557154422b38526b6a4342784c71794e74617a6936794f0a3458646f777833374b55745a4e4d773348354467394b6e6a4d3774644a674e4978726736786d2f6736546f7379426946474761332f7a77616e6d674956384d2b0a46796f4f474f784563515078677a527a57364d544d424577447759445652305242416777426f6345667741414154414b42676771686b6a4f5051514442414e6e0a4144426b416a4150305a63492b7947315a4c42414859334e432b37633054753767545459336e376b356d785548344c314263726b6f49387545314f77502f53780a73784769717863434d41742f5348434b4b39766a414d344530774b483944514d4f57766d7a384e70657850376d58585a755a6b3776544c6945652b66725259500a5a6b4548794c2b7944513d3d0a2d2d2d2d2d454e442043455254494649434154452d2d2d2d2d0a0000000e3132372e302e302e313a0000c79e',
|
||||
'0002f7edc8fa1ecc32967f827c9043fcae6ba73afa5c000000123a74656d706f726172792d646f6d61696e3a5f5f499231b6ce8731d78f2992daf122ecf1c2eb62ecc916787aa46325912e287a95ffedcdab54b9a81919385b8514f7987fd4d2decc2f2aa2c8aad8abb01d09e041fbe0000000418e88e6db3545de3e559ed94c80e539061f6831f00d406282f23c0713fc22c089422ee743a1fd07fc8a4b43ad4220cf13461906a7144ab55097febd1c7c03275a1b02080211dbeb2a5ea8af36c4c8b2f99b26202c5d9f18b76ead528bb88751243770029d35389f46fd6bbcc7d72f7fe225dc5526607933f37ffbf208f7832f943bc23c000002cd2d2d2d2d2d424547494e2043455254494649434154452d2d2d2d2d0a4d4949423554434341577167417749424167495562326461702b336741476234554b767141412b54574b767832657377436759494b6f5a497a6a3045417751770a535445534d424147413155454177774a4d5449334c6a41754d4334784d544d774d5159445651524244436f77654559335257526a4f455a424d575644597a4d790a4f545933526a67794e304d354d44517a526d4e425a545a695954637a59575a424e574d774868634e4d6a41774f5445304d5441304e4449315768634e4d6a45770a4f5445304d5441304e444931576a424a4d524977454159445651514444416b784d6a63754d4334774c6a45784d7a417842674e564245454d4b6a4234526a64460a5a474d34526b45785a554e6a4d7a49354e6a64474f444933517a6b774e444e475930466c4e6d4a684e7a4e685a6b4531597a42324d42414742797147534d34390a41674547425375424241416941324941424954312b31536d48336b5a5873305144654d5a32584f4d614a542b36567032424d682f46355567376e5072556654440a564f54384d3672506d4c70306b31467a6a397776344a2b583839707275797641625471714a346f6138705a474a37516d6666506152765a7a6b4f3776383879730a534a754147496e7a42696f7547714e5938364d544d424577447759445652305242416777426f6345667741414154414b42676771686b6a4f5051514442414e700a4144426d416a4541387158724e53533371756a4a664c2b3750592b6572395a6e4873674a2f587a484d42586657567362706d612f582f555832586278755574310a5a64766a686c6b53416a45416d4d7375393175776d6261754a5764707a33746b6e45776c427855696f7142464b6e684538766c4632445347434f3532693454340a557679696d657878666439570a2d2d2d2d2d454e442043455254494649434154452d2d2d2d2d0a0000000e3132372e302e302e313a0000c79f',
|
||||
'00024cceba2d7d2b4fdce4304d3e09a1fea9fbeb1528000000123a74656d706f726172792d646f6d61696e3a5f5f499292e0b1f461903c96d97cebb5104173a59a9a1a665198916d6cb2dc457f636e6cf96a0edb16625b21d1f036cc6c0c4504580287450a725112d3aa6543a08d9b070000004152797ba06824b91f0265a24aab9ce468e07640665c4f4cb2927b3b871b7481d6359726ad4f28ce3a55299bf9df12f81ba8934c5617032d22615495cad8df98371b03e2564124a21a3db73179e37c6134e7ac291b12ede6ea7212d859d949a2f8a95202988b67b8c03021ddaf78f115598bc93e62b53bed165546e1c4ddba0e2bcadcd1000002cd2d2d2d2d2d424547494e2043455254494649434154452d2d2d2d2d0a4d4949423544434341577167417749424167495547416f3543374f77505a73416f444c474c4f5331474f7062634d6b77436759494b6f5a497a6a3045417751770a535445534d424147413155454177774a4d5449334c6a41754d4334784d544d774d5159445651524244436f77654452445132564359544a6b4e305179516a526d0a5a474e464e444d774e47517a5a5441355954466d5a5745355a6d4a46596a45314d6a67774868634e4d6a41774f5445304d5441304e4449335768634e4d6a45770a4f5445304d5441304e444933576a424a4d524977454159445651514444416b784d6a63754d4334774c6a45784d7a417842674e564245454d4b6a42344e454e440a5a554a684d6d513352444a434e475a6b593055304d7a41305a444e6c4d446c684d575a6c59546c6d596b56694d5455794f4442324d42414742797147534d34390a41674547425375424241416941324941424b6870763744544c41696976596d77325238355259574273476253536f53757362414e304765793938462b413877550a4d5546686f2f5a6b46753236352f6b46337137704173724d38374f386e73487451726e53365964374c325a4b73752b47494d6b4e564d316b6346334e386d52540a7547546d45633970364130356341524958714d544d424577447759445652305242416777426f6345667741414154414b42676771686b6a4f5051514442414e6f0a4144426c416a4238667a4274764f4a51473336694b464454766170624d69334a523564786337636f684863514e6c657a3744497430726c33454978683379754e0a49747a52447441434d514375566e577155622b4e2f637771636a634a47745447654c3178414f49554f42556958524e6178485358613830742f722b49714365310a4e3931736f6847444554413d0a2d2d2d2d2d454e442043455254494649434154452d2d2d2d2d0a0000000e3132372e302e302e313a0000c7a0',
|
||||
'00023da8d322cb2435da26e9c9fee670f9fb7fe74e49000000123a74656d706f726172792d646f6d61696e3a5f5f49924981395a9540133bf1579d81c19a327087580ed8d4d5e94090c2b8c2c101e28fbb21d3e7fa2ee4802581f43b52560b55ff5c9b1dc14b64abc05b296fc1f5e48b000000410329a47aa9af11d0ae6dde6780f8b59295a63264b5f44607a9cb2a887e8f63ef07e3488d863770aa422952edbe18c97219b5c5e2df43692d9a3f64cd394fa5c01b02e70b19d70b8bd56d5040479ae8d6a94f862988357be591e7595d02b1f654c46e028ebe1b69801012c4bda06e522ef71e4386f789b969c4f4a9fa9c78ffe9ec69e0000002cd2d2d2d2d2d424547494e2043455254494649434154452d2d2d2d2d0a4d49494235444343415771674177494241674955514b6c4e732f7032556576392b466a4b334a32626f53666358454177436759494b6f5a497a6a3045417751770a535445534d424147413155454177774a4d5449334c6a41754d4334784d544d774d5159445651524244436f7765444e45515468454d7a4979513049794e444d310a5a4545794e6b5535517a6c6d525555324e7a426d4f575a434e305a6c4e7a52464e446b774868634e4d6a41774f5445304d5441304e4449345768634e4d6a45770a4f5445304d5441304e444934576a424a4d524977454159445651514444416b784d6a63754d4334774c6a45784d7a417842674e564245454d4b6a42344d3052420a4f45517a4d6a4a44516a49304d7a566b5154493252546c444f575a46525459334d4759355a6b4933526d55334e4555304f5442324d42414742797147534d34390a41674547425375424241416941324941424a4c4947745a48532b63376f753348553435546142326e2b503836714c6e67506f476269305037645461524c3437650a78542b2f4a4f64766a3877707453557957467662732b71697756346f334d3451395a3353764e6f77776b42646a4d6139687052596c50324a43676234474e34720a626573335a547137354769785741396641364d544d424577447759445652305242416777426f6345667741414154414b42676771686b6a4f5051514442414e6f0a4144426c416a4541717733684d7667324b4842397139516736436e33513041514f5a654b414e51393174666c64303642352f426b494248737031357a616e73610a6a63445770344133416a41584835445465334d5364556e62477a2f667435746f632b7a4b4f2b41655344514e345247786b667a68414f537564455a646738714e0a6f676645506a32314735493d0a2d2d2d2d2d454e442043455254494649434154452d2d2d2d2d0a0000000e3132372e302e302e313a0000c7a1',
|
||||
'0002dbc23ae43a150ff8884b02cea117b22d1c3b9796000000123a74656d706f726172792d646f6d61696e3a5f5f4992decb80e20efa366b3f73986040d5386c68ae76c30f7ee590af1b0e8a9863d69c6b9743f20c69e9253744d5346ce427505677439149738ef9419cfb6399182a3100000041ce404e18af2bd8fb27c5a84c9a9d34a32d21f13b5aad9af6b56dc9636f85ef2048dd9e200a580ea726282f7858df36465bf377f78b165bb92bcb0f1f1bf9b4d91b03489e298796bf184baabe5d6b8f069762fd6469a44c31edbdd39fab75554233200233066c9f661c6e8f746eed7c3d7ffb30e89c293161cc3ae9c7f69d6680de1c2c000002cd2d2d2d2d2d424547494e2043455254494649434154452d2d2d2d2d0a4d49494235444343415771674177494241674955645567397774633035376872796a326746614f4a4f73656539414977436759494b6f5a497a6a3045417751770a535445534d424147413155454177774a4d5449334c6a41754d4334784d544d774d5159445651524244436f7765455269597a497a515555304d3245784e54426d0a5a6a67344f4452434d444a445a5745784d5464694d6a4a454d574d7a596a6b334f5459774868634e4d6a41774f5445304d5441304e444d775768634e4d6a45770a4f5445304d5441304e444d77576a424a4d524977454159445651514444416b784d6a63754d4334774c6a45784d7a417842674e564245454d4b6a423452474a6a0a4d6a4e425254517a595445314d475a6d4f4467344e4549774d6b4e6c595445784e3249794d6b5178597a4e694f5463354e6a42324d42414742797147534d34390a416745474253754242414169413249414241386c3843413243596861744a7039686d436a4768576c4d31436b456b504436554c4a617a2f2b4a322f6f5737504a0a4254466d747265643948645a457659335a575a356b73795350556c774d4b7a655a644b746a6b566758482f50575733347137616f59586d4e63615874744c48410a754876744c6b4c73776c454c457334674a364d544d424577447759445652305242416777426f6345667741414154414b42676771686b6a4f5051514442414e6f0a4144426c416a41374f73695667467567556f422f3971477879796a2b44494e3176345649623145434d5838466158303930416a50387437504961615242664f580a356743792f5a41434d51436a2b6c4f37706138496534414d32674751324f6b4b6f4d4775766a476a6e7349584b6a51685479644e34396e6136354262564459330a4d313475544a62453451413d0a2d2d2d2d2d454e442043455254494649434154452d2d2d2d2d0a0000000e3132372e302e302e313a0000c7a2',
|
||||
'000268e527780872cda0216ba0d8fbd58b67a5d5e351000000123a74656d706f726172792d646f6d61696e3a5f5f4992a725ac643b499c11d1d80bf23bfd9171f02052afa9f3de09a982eec2fec32caa15da3785c2ab227e55c53e14e8c676d6db7b8ebe7cf11679214c3c59b708f7bf000000413adde5df9dffe3d93b658ba9ec523db38d76534caad41c4143eba662c2b81f5d0387c2ed9802c68a48087d8b8712b1f3dd59a2edc5c4d6232b085e411d9d1e6e1c02b257de9a118e9bfaf7b1d4f65117aae3b5345058b8234c78d5354da6da76616d0241b7c08af5a62f6db9e5db720b0bc1dae44b039e994a362a57ed255808c20504000002cd2d2d2d2d2d424547494e2043455254494649434154452d2d2d2d2d0a4d494942354443434157716741774942416749554f394570544c35376433355a584c59534c46576b6334424644734177436759494b6f5a497a6a3045417751770a535445534d424147413155454177774a4d5449334c6a41754d4334784d544d774d5159445651524244436f7765445934525455794e7a63344d4467334d6d4e6b0a595441794d545a435954426b4f475a4352445534596a5933595456454e57557a4e5445774868634e4d6a41774f5445304d5441304e444d785768634e4d6a45770a4f5445304d5441304e444d78576a424a4d524977454159445651514444416b784d6a63754d4334774c6a45784d7a417842674e564245454d4b6a42344e6a68460a4e5449334e7a67774f446379593252684d4449784e6b4a684d4751345a6b4a454e5468694e6a64684e5551315a544d314d5442324d42414742797147534d34390a41674547425375424241416941324941424a78514566476e716257394e754476714b527a614869734f7443766f4b737a52397768415a3749484f76546d6d72700a5763564f3731585135427333476b796f5063304359773952766d64662b7337324e4e7a596d304f5456586930446558656c5a43646f5a3449677777586c7446390a326630713772352b3059585950556a6751714d544d424577447759445652305242416777426f6345667741414154414b42676771686b6a4f5051514442414e6f0a4144426c416a4541702f455a35717756312f73576f32514f37452f777863376f2b663959664735534b793151574e532f3349737464524772516f7347614344740a4a502b2b696c424d416a42514b43524e6343676d6c6b6f396e2f795444397259424638726e6d6167394e6f6d714b49465468626348616f625763504d33774d4d0a7a514c54326867754b65593d0a2d2d2d2d2d454e442043455254494649434154452d2d2d2d2d0a0000000e3132372e302e302e313a0000c7a3',
|
||||
'00025a83529ff76ac5723a87008c4d9b436ad4ca7d28000000123a74656d706f726172792d646f6d61696e3a5f5f4992754a5a73ad1f61d3764ce25d6f9d7f801a0415eb00e5500c556909712d2f01c48aabb3ea646dd66ed3f57dc0c9f2f8b5e6b8e2db020acdca1c2a8e501f49115500000041812d1cbbdf46721689f7c638b3a99ca261761995de8c162bf9d135785d83f7041a2e3d81e3058e88cc45db3dfabd89a8c15f7ae2e53322e51ac616a690714a021c0391c79579899bbf801fe2cdf2b1c6ac8b3a0d09d60bdf6583107eaa7f2dac969e03f756ab6504ae93cee52b7b0051b364b45661d523f8cd16bef42c2326ab92791f000002cd2d2d2d2d2d424547494e2043455254494649434154452d2d2d2d2d0a4d4949423554434341577167417749424167495553555158626833423974714f6a4169566b6b626c6f54316744393877436759494b6f5a497a6a3045417751770a535445534d424147413155454177774a4d5449334c6a41754d4334784d544d774d5159445651524244436f77654456424f444d314d6a6c6d5a6a633251574d310a4e7a497a515467334d444134597a52454f5549304d7a5a42524452445154646b4d6a67774868634e4d6a41774f5445304d5441304e444d795768634e4d6a45770a4f5445304d5441304e444d79576a424a4d524977454159445651514444416b784d6a63754d4334774c6a45784d7a417842674e564245454d4b6a42344e5545340a4d7a55794f575a6d4e7a5a42597a55334d6a4e424f4463774d44686a4e455135516a517a4e6b46454e454e424e3251794f4442324d42414742797147534d34390a4167454742537542424141694132494142495736624f51794d68516e793658714657396637374a65635231784a636256357a726f344e472f77577663614a76740a7275686767375839672f614b6367664b5964756d506d682f4170344261736249527832664664514b3342645054474b356a4e30646458365231346144315a46450a4c3266574d7432444f644746416e6a7430364d544d424577447759445652305242416777426f6345667741414154414b42676771686b6a4f5051514442414e700a4144426d416a45416b362f374b746a6a4a496f596d634377544a71577172322b54472b6e4f7065454f357a614f736d53695651657061703343353156523658790a774f2f464671684e416a454178563174315952524645425749375857704d4d56413858586f4f664f49716237796851676a387874644d4f386c51374b566277580a314e7a30537068436367345a0a2d2d2d2d2d454e442043455254494649434154452d2d2d2d2d0a0000000e3132372e302e302e313a0000c7a4',
|
||||
'00028735015837bd10e05d9cf5ea43a2486bf4be156f000000123a74656d706f726172792d646f6d61696e3a5f5f4992894077589d998f80e69a81164b5739d92e9cfa74d7d58120bddea716338c756d171f2a034227c2d9923529646e00b55fc86d9962b1e7e7640d367e5ef8f51f6a00000041dcca8fe98d7f394b64dac38671b0553c4116c14c5cbc6bc79b10ab39668475d312cb51596e3930a13b9d0979921dc84812ea26d12fba604a9af865f763fa27de1c03ed03e383d8acde399a630fdee226bfaefcdc55598ad3132e124af568f0b85c6b026aa8aa2e53aa80c7521d73a6f9e02b90e5a0cd8b5c9eee70f28af7447f811a74000002cd2d2d2d2d2d424547494e2043455254494649434154452d2d2d2d2d0a4d494942347a43434157716741774942416749554b616974387064592f565a6a55686f672f7937344447724258423877436759494b6f5a497a6a3045417751770a535445534d424147413155454177774a4d5449334c6a41754d4334784d544d774d5159445651524244436f77654467334d7a55774d5455344d7a6469524445770a5a5441315a446c6a5a6a56465154517a515449304f445a435a6a52435a5445314e6b59774868634e4d6a41774f5445304d5441304e444d7a5768634e4d6a45770a4f5445304d5441304e444d7a576a424a4d524977454159445651514444416b784d6a63754d4334774c6a45784d7a417842674e564245454d4b6a42344f44637a0a4e5441784e54677a4e324a454d54426c4d44566b4f574e6d4e5556424e444e424d6a51344e6b4a6d4e454a6c4d545532526a42324d42414742797147534d34390a41674547425375424241416941324941424e6a6e6c73487576466f3367594f585152347a414f316c4f35304c684d715a6c5132517268307143665146506e68770a627433316738515351634e6672674d44754b575474734869364263335839697275712f4a374a6f6774302f623957636334666e70735879556e5443375377627a0a6c325635566c4c4b5232446754416a416e714d544d424577447759445652305242416777426f6345667741414154414b42676771686b6a4f5051514442414e6e0a4144426b416a414b6f6e4a63353533786f2b4343743863743541544a7754546f4d54746b7047773454416f543365674f6f556a706f41544a65724f66426b45410a37416f54594e4d434d4263786b6978334f344d31356b68704a6b4e4f47434150555a6f497939366e6f366530427754387859466e4f31547762476477337876770a314d3477556c666251773d3d0a2d2d2d2d2d454e442043455254494649434154452d2d2d2d2d0a0000000e3132372e302e302e313a0000c7a5'
|
||||
)
|
||||
|
||||
versioned_ursulas = {
|
||||
1: ursulas_v1,
|
||||
2: ursulas_v2
|
||||
}
|
||||
|
||||
|
||||
def test_deserialize_ursulas_version_1():
|
||||
"""
|
||||
DON'T 'FIX' THIS TEST IF FAILING, UNLESS YOU KNOW WHAT YOU'RE DOING.
|
||||
The goal of this test is to show incompatibility of current Discovery Loop version wrt to version 1.
|
||||
See issue #1869 "Test with a hard-coded, versioned node metadata bytestring"
|
||||
https://github.com/nucypher/nucypher/issues/1869
|
||||
"""
|
||||
|
||||
expected_version = 1
|
||||
ursulas_matrix = versioned_ursulas[expected_version]
|
||||
for fossilized_ursula in ursulas_matrix:
|
||||
fossilized_ursula = bytes.fromhex(fossilized_ursula)
|
||||
|
||||
version, _ = Ursula.version_splitter(fossilized_ursula, return_remainder=True)
|
||||
assert version == expected_version
|
||||
assert version != Ursula.LEARNER_VERSION
|
||||
|
||||
with pytest.raises(Teacher.AreYouFromThePast, match=f"purported to be of version 1, "
|
||||
f"but we're version {Ursula.LEARNER_VERSION}"):
|
||||
_resurrected_ursula = Ursula.from_bytes(fossilized_ursula, fail_fast=True)
|
||||
|
||||
assert UNKNOWN_VERSION == Ursula.from_bytes(fossilized_ursula, fail_fast=False)
|
||||
|
||||
|
||||
def test_deserialize_ursulas_version_2():
|
||||
"""
|
||||
DON'T 'FIX' THIS TEST IF FAILING, UNLESS YOU KNOW WHAT YOU'RE DOING.
|
||||
The goal of this test is to show compatibility of a hard-coded version 2 bytestring.
|
||||
See issue #1869 "Test with a hard-coded, versioned node metadata bytestring"
|
||||
https://github.com/nucypher/nucypher/issues/1869
|
||||
"""
|
||||
|
||||
expected_version = 2
|
||||
ursulas_matrix = versioned_ursulas[expected_version]
|
||||
for fossilized_ursula in ursulas_matrix:
|
||||
fossilized_ursula = bytes.fromhex(fossilized_ursula)
|
||||
|
||||
version, _ = Ursula.version_splitter(fossilized_ursula, return_remainder=True)
|
||||
assert version == expected_version
|
||||
assert version == Ursula.LEARNER_VERSION
|
||||
|
||||
resurrected_ursula = Ursula.from_bytes(fossilized_ursula, fail_fast=True)
|
||||
assert TEMPORARY_DOMAIN.encode('utf-8') == resurrected_ursula.domain
|
|
@ -70,8 +70,8 @@ def test_bob_can_retreive_the_treasure_map_and_decrypt_it(enacted_federated_poli
|
|||
that Bob can retrieve it with only the information about which he is privy pursuant to the PolicyGroup.
|
||||
"""
|
||||
bob = enacted_federated_policy.bob
|
||||
_previous_domains = bob.learning_domains
|
||||
bob.learning_domains = [] # Bob has no knowledge of the network.
|
||||
_previous_domain = bob.learning_domain
|
||||
bob.learning_domain = None # Bob has no knowledge of the network.
|
||||
|
||||
# Of course, in the real world, Bob has sufficient information to reconstitute a PolicyGroup, gleaned, we presume,
|
||||
# through a side-channel with Alice.
|
||||
|
@ -84,7 +84,7 @@ def test_bob_can_retreive_the_treasure_map_and_decrypt_it(enacted_federated_poli
|
|||
|
||||
# Bob finds out about one Ursula (in the real world, a seed node, hardcoded based on his learning domain)
|
||||
bob.done_seeding = False
|
||||
bob.learning_domains = _previous_domains
|
||||
bob.learning_domain = _previous_domain
|
||||
|
||||
# ...and then learns about the rest of the network.
|
||||
bob.learn_from_teacher_node(eager=True)
|
||||
|
|
|
@ -197,7 +197,7 @@ class NotARestApp:
|
|||
if self._actual_rest_app is None:
|
||||
self._actual_rest_app, self._datastore = make_rest_app(db_filepath=tempfile.mkdtemp(),
|
||||
this_node=self.this_node,
|
||||
serving_domains=(None,))
|
||||
serving_domain=None)
|
||||
_new_view_functions = self._ViewFunctions(self._actual_rest_app.view_functions)
|
||||
self._actual_rest_app.view_functions = _new_view_functions
|
||||
self._actual_rest_apps.append(
|
||||
|
@ -235,7 +235,7 @@ mock_metadata_validation = patch("nucypher.network.nodes.Teacher.validate_metada
|
|||
|
||||
@contextmanager
|
||||
def mock_secret_source(*args, **kwargs):
|
||||
with patch("nucypher.datastore.keypairs.Keypair._private_key_source", new=lambda *args, **kwargs: NotAPrivateKey()):
|
||||
with patch("nucypher.crypto.keypairs.Keypair._private_key_source", new=lambda *args, **kwargs: NotAPrivateKey()):
|
||||
yield
|
||||
NotAPublicKey.reset()
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ import sha3
|
|||
from constant_sorrow.constants import PUBLIC_ONLY
|
||||
from umbral.keys import UmbralPrivateKey
|
||||
|
||||
from nucypher.datastore import keypairs
|
||||
from nucypher.crypto import keypairs
|
||||
|
||||
|
||||
def test_gen_keypair_if_needed():
|
|
@ -21,10 +21,12 @@ import pytest
|
|||
import tempfile
|
||||
from datetime import datetime
|
||||
|
||||
from nucypher.datastore import datastore, keypairs
|
||||
from nucypher.crypto import keypairs
|
||||
from nucypher.datastore import datastore
|
||||
from nucypher.datastore.base import DatastoreRecord, RecordField
|
||||
from nucypher.datastore.models import PolicyArrangement, Workorder
|
||||
|
||||
|
||||
class TestRecord(DatastoreRecord):
|
||||
_test = RecordField(bytes)
|
||||
_test_date = RecordField(datetime,
|
||||
|
@ -152,6 +154,7 @@ def test_datastore_describe():
|
|||
with storage.describe(TestRecord, 'new_id') as new_test_record:
|
||||
assert new_test_record.test == b'now it exists :)'
|
||||
|
||||
|
||||
def test_datastore_query_by():
|
||||
temp_path = tempfile.mkdtemp()
|
||||
storage = datastore.Datastore(temp_path)
|
||||
|
@ -250,6 +253,7 @@ def test_datastore_query_by():
|
|||
with pytest.raises(datastore.RecordNotFound):
|
||||
with storage.query_by(NoRecord, writeable=True) as records:
|
||||
assert len(records) == 'this never gets executed'
|
||||
|
||||
|
||||
def test_datastore_record_read():
|
||||
db_env = lmdb.open(tempfile.mkdtemp())
|
||||
|
|
|
@ -26,7 +26,7 @@ from tests.utils.ursula import MOCK_URSULA_STARTING_PORT
|
|||
|
||||
TEST_CHARACTER_CONFIG_BASE_PARAMS = dict(
|
||||
dev_mode=True,
|
||||
domains={TEMPORARY_DOMAIN},
|
||||
domain=TEMPORARY_DOMAIN,
|
||||
start_learning_now=False,
|
||||
abort_on_learning_error=True,
|
||||
save_metadata=False,
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
"""
|
||||
This file is part of nucypher.
|
||||
|
||||
nucypher is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
nucypher is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with nucypher. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
|
||||
from nucypher.network.nodes import Learner
|
||||
|
||||
|
||||
@pytest.mark.skip
|
||||
def test_print_ursulas_bytes(blockchain_ursulas):
|
||||
"""
|
||||
Helper test that can be manually executed to get version-specific ursulas' metadata,
|
||||
which can be later used in tests/integration/learning/test_learning_versions.py
|
||||
"""
|
||||
|
||||
print(f"\nursulas_v{Learner.LEARNER_VERSION} = (")
|
||||
for ursula in blockchain_ursulas:
|
||||
print(f" '{bytes(ursula).hex()}',")
|
||||
print(")")
|
Loading…
Reference in New Issue