Merge pull request #1881 from nucypher/blue-oyster-mushrooms

Blue oyster mushrooms: Evolving node validity, optimization, domain separation, and migration.
pull/2304/head
K Prasch 2020-09-25 12:40:57 -07:00 committed by GitHub
commit 9b45c08ac4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
57 changed files with 554 additions and 322 deletions

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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:]):

View File

@ -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,

View File

@ -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.

View File

@ -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

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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

View File

@ -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."

View File

@ -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)

View File

@ -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"

View File

@ -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,

View 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

View File

@ -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))

View File

@ -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__()}

View File

@ -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

View File

@ -29,4 +29,3 @@ BLAKE2B = hashes.BLAKE2b(64)
# SECP256K1
CAPSULE_LENGTH = 98
PUBLIC_KEY_LENGTH = 33
PUBLIC_ADDRESS_LENGTH = 20

View File

@ -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):

View File

@ -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

View File

@ -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)

View File

@ -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:

View File

@ -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:

View File

@ -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.

View File

@ -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))
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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)

View File

@ -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,

View File

@ -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

View File

@ -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,

View File

@ -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()

View File

@ -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

View File

@ -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)

View File

@ -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.

View File

@ -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}

View File

@ -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")

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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()

View File

@ -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():

View File

@ -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())

View File

@ -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,

33
tests/utils/versions.py Normal file
View File

@ -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(")")