mirror of https://github.com/nucypher/nucypher.git
fully deprecates the node storage API
parent
c98e31c366
commit
1fcf3c84a8
|
@ -1 +1 @@
|
|||
Peer TLS certificates a no longer stored on the node's disk.
|
||||
Peer TLS certificates are no longer stored on the node's disk.
|
||||
|
|
|
@ -90,7 +90,6 @@ from nucypher.characters.banners import (
|
|||
URSULA_BANNER,
|
||||
)
|
||||
from nucypher.characters.base import Character, Learner
|
||||
from nucypher.config.storages import NodeStorage
|
||||
from nucypher.crypto.keypairs import HostingKeypair
|
||||
from nucypher.crypto.powers import (
|
||||
DecryptingPower,
|
||||
|
@ -1231,10 +1230,6 @@ class Ursula(Teacher, Character, Operator):
|
|||
|
||||
return potential_seed_node
|
||||
|
||||
@classmethod
|
||||
def from_storage(cls, node_storage: NodeStorage, checksum_adress: str) -> "Ursula":
|
||||
return node_storage.get(checksum_address=checksum_adress)
|
||||
|
||||
#
|
||||
# Properties
|
||||
#
|
||||
|
|
|
@ -371,18 +371,6 @@ def destroy(general_config, config_options, config_file, force):
|
|||
destroy_configuration(emitter, character_config=ursula_config, force=force)
|
||||
|
||||
|
||||
@ursula.command()
|
||||
@group_config_options
|
||||
@option_config_file
|
||||
@group_general_config
|
||||
def forget(general_config, config_options, config_file):
|
||||
"""Forget all known nodes."""
|
||||
emitter = setup_emitter(general_config, config_options.operator_address)
|
||||
_pre_launch_warnings(emitter, dev=config_options.dev, force=None)
|
||||
ursula_config = config_options.create_config(emitter, config_file)
|
||||
forget_nodes(emitter, configuration=ursula_config)
|
||||
|
||||
|
||||
@ursula.command()
|
||||
@group_character_options
|
||||
@option_config_file
|
||||
|
|
|
@ -25,7 +25,6 @@ from nucypher.blockchain.eth.registry import (
|
|||
from nucypher.blockchain.eth.signers import Signer
|
||||
from nucypher.characters.lawful import Ursula
|
||||
from nucypher.config import constants
|
||||
from nucypher.config.storages import NodeStorage
|
||||
from nucypher.config.util import cast_paths_from
|
||||
from nucypher.crypto.keystore import Keystore
|
||||
from nucypher.crypto.powers import CryptoPower, CryptoPowerUp
|
||||
|
@ -328,7 +327,7 @@ class CharacterConfiguration(BaseConfiguration):
|
|||
'Sideways Engagement' of Character classes; a reflection of input parameters.
|
||||
"""
|
||||
|
||||
VERSION = 8 # bump when static payload scheme changes
|
||||
VERSION = 9 # bump when static payload scheme changes
|
||||
|
||||
CHARACTER_CLASS = NotImplemented
|
||||
MNEMONIC_KEYSTORE = False
|
||||
|
@ -384,7 +383,6 @@ class CharacterConfiguration(BaseConfiguration):
|
|||
lonely: bool = False,
|
||||
# Node Storage
|
||||
known_nodes: Optional[set] = None,
|
||||
node_storage: Optional[NodeStorage] = None,
|
||||
reload_metadata: bool = True,
|
||||
save_metadata: bool = True,
|
||||
# Blockchain
|
||||
|
@ -500,13 +498,11 @@ class CharacterConfiguration(BaseConfiguration):
|
|||
|
||||
if dev_mode:
|
||||
self.__temp_dir = UNINITIALIZED_CONFIGURATION
|
||||
self._setup_node_storage()
|
||||
self.initialize(password=DEVELOPMENT_CONFIGURATION)
|
||||
else:
|
||||
self.__temp_dir = LIVE_CONFIGURATION
|
||||
self.config_root = config_root or self.DEFAULT_CONFIG_ROOT
|
||||
self._cache_runtime_filepaths()
|
||||
self._setup_node_storage(node_storage=node_storage)
|
||||
|
||||
# Network
|
||||
self.network_middleware = network_middleware or self.DEFAULT_NETWORK_MIDDLEWARE(
|
||||
|
@ -604,15 +600,6 @@ class CharacterConfiguration(BaseConfiguration):
|
|||
def dev_mode(self) -> bool:
|
||||
return self.__dev_mode
|
||||
|
||||
def _setup_node_storage(self, node_storage: Optional = None) -> None:
|
||||
node_storage = node_storage or NodeStorage()
|
||||
self.node_storage = node_storage
|
||||
|
||||
def forget_nodes(self) -> None:
|
||||
self.node_storage.clear()
|
||||
message = "Removed all stored node node metadata and certificates"
|
||||
self.log.debug(message)
|
||||
|
||||
def destroy(self) -> None:
|
||||
"""Parse a node configuration and remove all associated files from the filesystem"""
|
||||
self.config_file_location.unlink()
|
||||
|
@ -744,7 +731,6 @@ class CharacterConfiguration(BaseConfiguration):
|
|||
network_middleware=self.network_middleware
|
||||
or self.DEFAULT_NETWORK_MIDDLEWARE(),
|
||||
known_nodes=self.known_nodes,
|
||||
node_storage=self.node_storage,
|
||||
keystore=self.keystore,
|
||||
crypto_power_ups=self.derive_node_power_ups(),
|
||||
)
|
||||
|
@ -855,16 +841,6 @@ class CharacterConfiguration(BaseConfiguration):
|
|||
|
||||
return self.keystore
|
||||
|
||||
@classmethod
|
||||
def load_node_storage(cls, storage_payload: dict):
|
||||
from nucypher.config.storages import NodeStorage
|
||||
|
||||
node_storage_subclasses = {storage._name: storage for storage in [NodeStorage]}
|
||||
storage_type = storage_payload[NodeStorage._TYPE_LABEL]
|
||||
storage_class = node_storage_subclasses[storage_type]
|
||||
node_storage = storage_class()
|
||||
return node_storage
|
||||
|
||||
def configure_pre_payment_method(self):
|
||||
# TODO: finalize config fields
|
||||
#
|
||||
|
|
|
@ -6,6 +6,7 @@ from .configuration_v4_to_v5 import configuration_v4_to_v5
|
|||
from .configuration_v5_to_v6 import configuration_v5_to_v6
|
||||
from .configuration_v6_to_v7 import configuration_v6_to_v7
|
||||
from .configuration_v7_to_v8 import configuration_v7_to_v8
|
||||
from .configuration_v8_to_v9 import configuration_v8_to_v9
|
||||
|
||||
MIGRATIONS = OrderedDict(
|
||||
{
|
||||
|
@ -16,5 +17,6 @@ MIGRATIONS = OrderedDict(
|
|||
(5, 6): configuration_v5_to_v6,
|
||||
(6, 7): configuration_v6_to_v7,
|
||||
(7, 8): configuration_v7_to_v8,
|
||||
(8, 9): configuration_v8_to_v9,
|
||||
}
|
||||
)
|
||||
|
|
|
@ -1,53 +0,0 @@
|
|||
from nucypher.blockchain.eth.decorators import validate_checksum_address
|
||||
from nucypher.crypto.signing import SignatureStamp
|
||||
from nucypher.utilities.logging import Logger
|
||||
|
||||
|
||||
class NodeStorage:
|
||||
_TYPE_LABEL = "storage_type"
|
||||
_name = ":memory:"
|
||||
|
||||
class NodeStorageError(Exception):
|
||||
pass
|
||||
|
||||
class UnknownNode(NodeStorageError):
|
||||
pass
|
||||
|
||||
def __init__(self, character_class=None):
|
||||
self.__metadata = dict()
|
||||
from nucypher.characters.lawful import Ursula
|
||||
|
||||
self.character_class = character_class or Ursula
|
||||
self.log = Logger(self.__class__.__name__)
|
||||
|
||||
def __getitem__(self, item):
|
||||
return self.get(checksum_address=item)
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
return self.set(node=value)
|
||||
|
||||
def __iter__(self):
|
||||
return self.all()
|
||||
|
||||
def all(self) -> set:
|
||||
return set(self.__metadata.values())
|
||||
|
||||
@validate_checksum_address
|
||||
def get(self, host: str = None, stamp: SignatureStamp = None):
|
||||
if not bool(stamp) ^ bool(host):
|
||||
message = "Either pass stamp or host; Not both. Got ({} {})".format(
|
||||
stamp, host
|
||||
)
|
||||
raise ValueError(message)
|
||||
try:
|
||||
return self.__metadata[stamp or host]
|
||||
except KeyError:
|
||||
raise self.UnknownNode
|
||||
|
||||
def set(self, node) -> bytes:
|
||||
self.__metadata[node.stamp] = node
|
||||
return self.__metadata[node.stamp]
|
||||
|
||||
def clear(self) -> None:
|
||||
"""Forget all stored nodes and certificates"""
|
||||
self.__metadata = dict()
|
|
@ -30,7 +30,6 @@ from nucypher.blockchain.eth.constants import NULL_ADDRESS
|
|||
from nucypher.blockchain.eth.domains import TACoDomain
|
||||
from nucypher.blockchain.eth.registry import ContractRegistry
|
||||
from nucypher.config.constants import SeednodeMetadata
|
||||
from nucypher.config.storages import NodeStorage
|
||||
from nucypher.crypto.powers import (
|
||||
CryptoPower,
|
||||
DecryptingPower,
|
||||
|
@ -218,9 +217,6 @@ class Learner:
|
|||
_LONG_LEARNING_DELAY = 90
|
||||
LEARNING_TIMEOUT = 10
|
||||
_ROUNDS_WITHOUT_NODES_AFTER_WHICH_TO_SLOW_DOWN = 10
|
||||
|
||||
# For Keeps
|
||||
__DEFAULT_NODE_STORAGE = NodeStorage
|
||||
__DEFAULT_MIDDLEWARE_CLASS = RestMiddleware
|
||||
|
||||
_crashed = (
|
||||
|
@ -257,7 +253,6 @@ class Learner:
|
|||
learn_on_same_thread: bool = False,
|
||||
known_nodes: tuple = None,
|
||||
seed_nodes: Tuple[tuple] = None,
|
||||
node_storage=None,
|
||||
save_metadata: bool = False,
|
||||
abort_on_learning_error: bool = False,
|
||||
lonely: bool = False,
|
||||
|
@ -288,11 +283,6 @@ class Learner:
|
|||
self._learning_deferred = None
|
||||
self._discovery_canceller = DiscoveryCanceller()
|
||||
|
||||
node_storage = node_storage or self.__DEFAULT_NODE_STORAGE()
|
||||
self.node_storage = node_storage
|
||||
if save_metadata and node_storage is NO_STORAGE_AVAILABLE:
|
||||
raise ValueError("Cannot save nodes without a configured node storage")
|
||||
|
||||
self.node_class = node_class or characters.lawful.Ursula
|
||||
|
||||
known_nodes = known_nodes or tuple()
|
||||
|
@ -342,9 +332,9 @@ class Learner:
|
|||
def known_nodes(self):
|
||||
return self.__known_nodes
|
||||
|
||||
def load_seednodes(self, read_storage: bool = True, record_fleet_state=False):
|
||||
def load_seednodes(self, record_fleet_state=False):
|
||||
"""
|
||||
Engage known nodes from storages and pre-fetch hardcoded seednode certificates for node learning.
|
||||
Pre-fetch hardcoded seednode certificates for node learning.
|
||||
|
||||
TODO: Dehydrate this with nucypher.utilities.seednodes.load_seednodes
|
||||
"""
|
||||
|
@ -404,38 +394,11 @@ class Learner:
|
|||
|
||||
self.done_seeding = True
|
||||
|
||||
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:
|
||||
self.known_nodes.record_fleet_state()
|
||||
|
||||
return discovered
|
||||
|
||||
def read_nodes_from_storage(self) -> List:
|
||||
stored_nodes = self.node_storage.all()
|
||||
|
||||
restored_from_disk = []
|
||||
invalid_nodes = defaultdict(list)
|
||||
for node in stored_nodes:
|
||||
if str(node.domain) != str(self.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.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,
|
||||
node,
|
||||
|
@ -464,9 +427,6 @@ class Learner:
|
|||
node
|
||||
) # FIXME - dont always remember nodes, bucket them.
|
||||
|
||||
if self.save_metadata:
|
||||
self.node_storage.set(node=node)
|
||||
|
||||
if eager:
|
||||
node.mature()
|
||||
registry = self.registry if self._verify_node_bonding else None
|
||||
|
|
|
@ -66,7 +66,7 @@ def _request(url: str, certificate=None) -> Union[str, None]:
|
|||
def _request_from_node(
|
||||
teacher,
|
||||
eth_endpoint: str,
|
||||
client: Optional["NucypherMiddlewareClient"] = None,
|
||||
client: Optional[NucypherMiddlewareClient] = None,
|
||||
timeout: int = 2,
|
||||
log: Logger = IP_DETECTION_LOGGER,
|
||||
) -> Union[str, None]:
|
||||
|
|
|
@ -130,83 +130,3 @@ def test_ursula_learns_via_cli(click_runner, ursulas, testerchain):
|
|||
|
||||
# Check that CLI Ursula reports that it remembers the teacher and saves the TLS certificate
|
||||
assert f"Saved TLS certificate for {LOOPBACK_ADDRESS}" in result.output
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="This test is poorly written and is failing.")
|
||||
@pt.inlineCallbacks
|
||||
def test_persistent_node_storage_integration(
|
||||
click_runner, custom_filepath, testerchain, ursulas, agency_local_registry, mocker
|
||||
):
|
||||
mocker.patch.object(ActiveRitualTracker, "start")
|
||||
(
|
||||
alice,
|
||||
ursula,
|
||||
another_ursula,
|
||||
staking_provider,
|
||||
*all_yall,
|
||||
) = testerchain.unassigned_accounts
|
||||
filename = UrsulaConfiguration.generate_filename()
|
||||
another_ursula_configuration_file_location = custom_filepath / filename
|
||||
|
||||
init_args = (
|
||||
"ursula",
|
||||
"init",
|
||||
"--eth-endpoint",
|
||||
TEST_ETH_PROVIDER_URI,
|
||||
"--polygon-endpoint",
|
||||
TEST_POLYGON_PROVIDER_URI,
|
||||
"--operator-address",
|
||||
another_ursula,
|
||||
"--domain",
|
||||
TEMPORARY_DOMAIN_NAME,
|
||||
"--rest-host",
|
||||
MOCK_IP_ADDRESS,
|
||||
"--config-root",
|
||||
str(custom_filepath.absolute()),
|
||||
"--registry-filepath",
|
||||
str(agency_local_registry.filepath.absolute()),
|
||||
)
|
||||
|
||||
envvars = {NUCYPHER_ENVVAR_KEYSTORE_PASSWORD: INSECURE_DEVELOPMENT_PASSWORD}
|
||||
result = click_runner.invoke(
|
||||
nucypher_cli, init_args, catch_exceptions=False, env=envvars
|
||||
)
|
||||
assert result.exit_code == 0
|
||||
|
||||
teacher = ursulas[-1]
|
||||
teacher_uri = teacher.rest_information()[0].uri
|
||||
|
||||
start_pytest_ursula_services(ursula=teacher)
|
||||
|
||||
user_input = f'{INSECURE_DEVELOPMENT_PASSWORD}\n'
|
||||
|
||||
run_args = ('ursula', 'run',
|
||||
'--dry-run',
|
||||
'--debug',
|
||||
'--config-file', str(another_ursula_configuration_file_location.absolute()),
|
||||
'--teacher', teacher_uri)
|
||||
|
||||
Operator.READY_TIMEOUT = 1
|
||||
with pytest.raises(Operator.ActorError):
|
||||
# Operator init success, but not bonded.
|
||||
result = yield threads.deferToThread(click_runner.invoke,
|
||||
nucypher_cli, run_args,
|
||||
catch_exceptions=False,
|
||||
input=user_input,
|
||||
env=envvars)
|
||||
assert result.exit_code == 0
|
||||
|
||||
# Run an Ursula amidst the other configuration files
|
||||
run_args = ('ursula', 'run',
|
||||
'--dry-run',
|
||||
'--debug',
|
||||
'--config-file', str(another_ursula_configuration_file_location.absolute()))
|
||||
|
||||
with pytest.raises(Operator.ActorError):
|
||||
# Operator init success, but not bonded.
|
||||
result = yield threads.deferToThread(click_runner.invoke,
|
||||
nucypher_cli, run_args,
|
||||
catch_exceptions=False,
|
||||
input=user_input,
|
||||
env=envvars)
|
||||
assert result.exit_code == 0
|
||||
|
|
|
@ -58,7 +58,6 @@ from tests.mock.interfaces import MockBlockchain
|
|||
from tests.mock.performance_mocks import (
|
||||
mock_cert_generation,
|
||||
mock_cert_loading,
|
||||
mock_cert_storage,
|
||||
mock_keep_learning,
|
||||
mock_message_verification,
|
||||
mock_record_fleet_state,
|
||||
|
|
|
@ -67,17 +67,6 @@ def config(request, mocker):
|
|||
mocker.resetall() # dont carry over context between functions
|
||||
|
||||
|
||||
def test_forget_cli_action(alice_test_config, test_emitter, mock_stdin, mocker, capsys):
|
||||
mock_forget = mocker.patch.object(CharacterConfiguration, 'forget_nodes')
|
||||
mock_stdin.line(YES)
|
||||
forget(emitter=test_emitter, configuration=alice_test_config)
|
||||
mock_forget.assert_called_once()
|
||||
assert mock_stdin.empty()
|
||||
captured = capsys.readouterr()
|
||||
assert CONFIRM_FORGET_NODES in captured.out
|
||||
assert SUCCESSFUL_FORGET_NODES in captured.out
|
||||
|
||||
|
||||
def test_update_configuration_cli_action(config, test_emitter, capsys):
|
||||
config_class, config_file = config.__class__, config.filepath
|
||||
updates = dict(domain=TEMPORARY_DOMAIN_NAME)
|
||||
|
|
|
@ -67,10 +67,6 @@ def test_initialize_via_cli(
|
|||
assert custom_filepath.is_dir(), 'Configuration file does not exist'
|
||||
assert (custom_filepath / 'keystore').is_dir(), 'Keystore does not exist'
|
||||
|
||||
# TODO: Only using in-memory node storage for now
|
||||
# assert (custom_filepath / 'known_nodes').is_dir(), 'known_nodes directory does not exist'
|
||||
assert not (custom_filepath / 'known_nodes').is_dir(), 'known_nodes directory does not exist'
|
||||
|
||||
|
||||
@pytest.mark.parametrize("config_class", CONFIG_CLASSES)
|
||||
def test_reconfigure_via_cli(
|
||||
|
|
|
@ -130,8 +130,6 @@ def test_corrupted_configuration(
|
|||
assert default_filename in top_level_config_root, "JSON configuration file was not created"
|
||||
|
||||
expected_fields = [
|
||||
# TODO: Only using in-memory node storage for now
|
||||
# 'known_nodes',
|
||||
'keystore',
|
||||
default_filename
|
||||
]
|
||||
|
|
|
@ -120,10 +120,6 @@ def test_initialize_custom_configuration_root(
|
|||
assert custom_filepath.is_dir(), 'Configuration file does not exist'
|
||||
assert (custom_filepath / 'keystore').is_dir(), 'KEYSTORE does not exist'
|
||||
|
||||
# TODO: Only using in-memory node storage for now
|
||||
# assert (custom_filepath / 'known_nodes').is_dir(), 'known_nodes directory does not exist'
|
||||
assert not (custom_filepath / 'known_nodes').is_dir(), 'known_nodes directory does not exist'
|
||||
|
||||
custom_config_filepath = custom_filepath / UrsulaConfiguration.generate_filename()
|
||||
assert custom_config_filepath.is_file(), 'Configuration file does not exist'
|
||||
|
||||
|
|
|
@ -14,7 +14,6 @@ from nucypher.config.characters import (
|
|||
UrsulaConfiguration,
|
||||
)
|
||||
from nucypher.config.constants import TEMPORARY_DOMAIN_NAME
|
||||
from nucypher.config.storages import NodeStorage
|
||||
from nucypher.crypto.keystore import Keystore
|
||||
from tests.constants import (
|
||||
INSECURE_DEVELOPMENT_PASSWORD,
|
||||
|
@ -75,10 +74,6 @@ def test_development_character_configurations(
|
|||
# Domain
|
||||
assert TEMPORARY_DOMAIN_NAME == str(thing_one.domain)
|
||||
|
||||
# Node Storage
|
||||
assert isinstance(thing_one.node_storage, NodeStorage)
|
||||
assert ":memory:" in thing_one.node_storage._name
|
||||
|
||||
# All development characters are unique
|
||||
_characters = [thing_one, thing_two]
|
||||
for _ in range(3):
|
||||
|
@ -187,8 +182,6 @@ def test_ursula_development_configuration(testerchain):
|
|||
# A Temporary Ursula
|
||||
port = ursula_one.rest_information()[0].port
|
||||
assert port == UrsulaConfiguration.DEFAULT_DEVELOPMENT_REST_PORT
|
||||
assert isinstance(ursula_one.node_storage, NodeStorage)
|
||||
assert ":memory:" in ursula_one.node_storage._name
|
||||
|
||||
# Alternate way to produce a character with a direct call
|
||||
ursula_two = config.produce()
|
||||
|
|
|
@ -1,88 +0,0 @@
|
|||
import pytest
|
||||
|
||||
from nucypher.blockchain.eth.signers.software import Web3Signer
|
||||
from nucypher.characters.lawful import Ursula
|
||||
from nucypher.config.constants import TEMPORARY_DOMAIN_NAME
|
||||
from nucypher.policy.payment import SubscriptionManagerPayment
|
||||
from nucypher.utilities.networking import LOOPBACK_ADDRESS
|
||||
from tests.constants import MOCK_ETH_PROVIDER_URI, TESTERCHAIN_CHAIN_ID
|
||||
from tests.utils.ursula import select_test_port
|
||||
|
||||
ADDITIONAL_NODES_TO_LEARN_ABOUT = 10
|
||||
|
||||
|
||||
def make_header(brand: bytes, major: int, minor: int) -> bytes:
|
||||
# Hardcoding this since it's too much trouble to expose it all the way from Rust
|
||||
assert len(brand) == 4
|
||||
major_bytes = major.to_bytes(2, 'big')
|
||||
minor_bytes = minor.to_bytes(2, 'big')
|
||||
header = brand + major_bytes + minor_bytes
|
||||
return header
|
||||
|
||||
|
||||
class BaseTestNodeStorageBackends:
|
||||
|
||||
character_class = Ursula
|
||||
storage_backend = NotImplemented
|
||||
|
||||
def _read_and_write_metadata(self, ursula, node_storage, operator_addresses, signer):
|
||||
# Write Node
|
||||
node_storage.set(node=ursula)
|
||||
|
||||
# Read Node
|
||||
node_from_storage = node_storage.get(stamp=ursula.stamp)
|
||||
assert ursula == node_from_storage, "Node storage {} failed".format(node_storage)
|
||||
|
||||
pre_payment_method = SubscriptionManagerPayment(
|
||||
blockchain_endpoint=MOCK_ETH_PROVIDER_URI, domain=TEMPORARY_DOMAIN_NAME
|
||||
)
|
||||
|
||||
# Save more nodes
|
||||
all_known_nodes = set()
|
||||
for i in range(ADDITIONAL_NODES_TO_LEARN_ABOUT):
|
||||
node = Ursula(
|
||||
rest_host=LOOPBACK_ADDRESS,
|
||||
rest_port=select_test_port(),
|
||||
domain=TEMPORARY_DOMAIN_NAME,
|
||||
signer=signer,
|
||||
eth_endpoint=MOCK_ETH_PROVIDER_URI,
|
||||
polygon_endpoint=MOCK_ETH_PROVIDER_URI,
|
||||
checksum_address=operator_addresses[i],
|
||||
operator_address=operator_addresses[i],
|
||||
pre_payment_method=pre_payment_method,
|
||||
condition_blockchain_endpoints={
|
||||
TESTERCHAIN_CHAIN_ID: [MOCK_ETH_PROVIDER_URI]
|
||||
},
|
||||
)
|
||||
node_storage.set(node=node)
|
||||
all_known_nodes.add(node)
|
||||
|
||||
# Read all nodes from storage
|
||||
all_stored_nodes = node_storage.all()
|
||||
all_known_nodes.add(ursula)
|
||||
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)
|
||||
|
||||
assert known_checksums == stored_checksums
|
||||
|
||||
# Read random nodes
|
||||
for i in range(3):
|
||||
random_node = all_known_nodes.pop()
|
||||
random_node_from_storage = node_storage.get(stamp=random_node.stamp)
|
||||
assert random_node.checksum_address == random_node_from_storage.checksum_address
|
||||
|
||||
return True
|
||||
|
||||
#
|
||||
# Storage Backend Tests
|
||||
#
|
||||
|
||||
@pytest.mark.usefixtures("mock_registry_sources")
|
||||
def test_read_and_write_to_storage(self, light_ursula, testerchain):
|
||||
assert self._read_and_write_metadata(ursula=light_ursula,
|
||||
node_storage=self.storage_backend,
|
||||
operator_addresses=testerchain.ursulas_accounts,
|
||||
signer=Web3Signer(testerchain.client))
|
||||
self.storage_backend.clear()
|
|
@ -1,15 +1,10 @@
|
|||
import pytest
|
||||
|
||||
import tests
|
||||
from nucypher.acumen.perception import FleetSensor
|
||||
from nucypher.blockchain.eth.domains import TACoDomain
|
||||
from nucypher.blockchain.eth.registry import ContractRegistry
|
||||
from nucypher.characters.lawful import Ursula
|
||||
from nucypher.config.storages import NodeStorage
|
||||
from nucypher.network.nodes import TEACHER_NODES
|
||||
from tests.constants import TEMPORARY_DOMAIN, TESTERCHAIN_CHAIN_INFO
|
||||
from tests.utils.registry import MockRegistrySource
|
||||
from tests.utils.ursula import make_ursulas
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
|
@ -105,155 +100,3 @@ def test_learner_learns_about_domains_separately(
|
|||
assert hero_learner in new_first_domain_learner.known_nodes
|
||||
assert other_first_domain_learner in new_first_domain_learner.known_nodes
|
||||
assert _nobody in new_first_domain_learner.known_nodes
|
||||
|
||||
|
||||
def test_learner_restores_metadata_from_storage(
|
||||
lonely_ursula_maker, tmpdir, domain_1, domain_2
|
||||
):
|
||||
# Create a local file-based node storage
|
||||
old_storage = NodeStorage()
|
||||
|
||||
# Use the ursula maker with this storage so it's populated with nodes from one domain
|
||||
_some_ursulas = lonely_ursula_maker(
|
||||
domain=domain_1,
|
||||
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=domain_2,
|
||||
node_storage=old_storage,
|
||||
quantity=2,
|
||||
know_each_other=True,
|
||||
save_metadata=False,
|
||||
)
|
||||
learner, buddy = new_learners
|
||||
buddy._Learner__known_nodes = FleetSensor(domain=domain_1)
|
||||
|
||||
# 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().domain == learner.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}
|
||||
|
||||
|
||||
def test_learner_ignores_stored_nodes_from_other_domains(
|
||||
lonely_ursula_maker,
|
||||
domain_1,
|
||||
domain_2,
|
||||
registry_1,
|
||||
registry_2,
|
||||
tmpdir,
|
||||
testerchain,
|
||||
ursula_test_config,
|
||||
):
|
||||
learner, other_staker = make_ursulas(
|
||||
ursula_test_config,
|
||||
domain=domain_1,
|
||||
registry=registry_1,
|
||||
quantity=2,
|
||||
know_each_other=True,
|
||||
staking_provider_addresses=testerchain.stake_providers_accounts[:2],
|
||||
operator_addresses=testerchain.ursulas_accounts[:2],
|
||||
)
|
||||
|
||||
pest, *other_ursulas_from_the_wrong_side_of_the_tracks = make_ursulas(
|
||||
ursula_test_config,
|
||||
domain=domain_2,
|
||||
registry=registry_2,
|
||||
quantity=5,
|
||||
know_each_other=True,
|
||||
staking_provider_addresses=testerchain.stake_providers_accounts[2:],
|
||||
operator_addresses=testerchain.ursulas_accounts[2:],
|
||||
)
|
||||
|
||||
assert pest not in other_staker.known_nodes
|
||||
assert pest not in learner.known_nodes
|
||||
pest._current_teacher_node = learner
|
||||
pest.learn_from_teacher_node()
|
||||
assert pest not in other_staker.known_nodes
|
||||
|
||||
##################################
|
||||
# Prior to #2423, learner remembered pest because POSTed node metadata was not domain-checked.
|
||||
# This is how ibex nodes initially made their way into mainnet fleet states.
|
||||
assert pest not in learner.known_nodes # But not anymore.
|
||||
|
||||
# Once pest made its way into learner, learner taught passed it to other mainnet nodes.
|
||||
assert pest not in learner.known_nodes # But not anymore.
|
||||
|
||||
learner.known_nodes.record_node(pest) # This used to happen anyway.
|
||||
|
||||
other_staker._current_teacher_node = learner
|
||||
other_staker.learn_from_teacher_node() # And once it did, the node from the wrong domain spread.
|
||||
assert pest not in other_staker.known_nodes # But not anymore.
|
||||
|
||||
|
||||
def test_learner_with_empty_storage_uses_fallback_nodes(
|
||||
lonely_ursula_maker, domain_1, mocker
|
||||
):
|
||||
mocker.patch.dict(TEACHER_NODES, {domain_1: ("teacher-uri",)}, clear=True)
|
||||
|
||||
# Create a learner and a teacher
|
||||
learner, teacher = lonely_ursula_maker(
|
||||
domain=domain_1, quantity=2, save_metadata=False
|
||||
)
|
||||
mocker.patch.object(Ursula, "from_teacher_uri", return_value=teacher)
|
||||
|
||||
# Since there are no nodes in local node storage, the learner should only learn about the teacher
|
||||
learner.learn_from_teacher_node()
|
||||
assert set(learner.known_nodes) == {teacher}
|
||||
|
||||
|
||||
def test_learner_uses_both_nodes_from_storage_and_fallback_nodes(
|
||||
lonely_ursula_maker,
|
||||
domain_1,
|
||||
registry_1,
|
||||
tmpdir,
|
||||
mocker,
|
||||
test_registry,
|
||||
ursula_test_config,
|
||||
testerchain,
|
||||
):
|
||||
mocker.patch.dict(TEACHER_NODES, {domain_1: ("teacher-uri",)}, clear=True)
|
||||
|
||||
# Create a local file-based node storage
|
||||
node_storage = NodeStorage()
|
||||
|
||||
# Create some nodes and persist them to local storage
|
||||
other_nodes = make_ursulas(
|
||||
ursula_test_config,
|
||||
domain=domain_1,
|
||||
registry=registry_1,
|
||||
node_storage=node_storage,
|
||||
know_each_other=True,
|
||||
quantity=3,
|
||||
save_metadata=True,
|
||||
staking_provider_addresses=testerchain.stake_providers_accounts[:3],
|
||||
operator_addresses=testerchain.ursulas_accounts[:3],
|
||||
)
|
||||
|
||||
# Create a teacher and a learner using existing node storage
|
||||
learner, teacher = lonely_ursula_maker(
|
||||
domain=domain_1,
|
||||
registry=registry_1,
|
||||
node_storage=node_storage,
|
||||
quantity=2,
|
||||
know_each_other=True,
|
||||
staking_provider_addresses=testerchain.stake_providers_accounts[3:],
|
||||
operator_addresses=testerchain.ursulas_accounts[3:],
|
||||
)
|
||||
# mocker.patch.object(Ursula, 'from_teacher_uri', return_value=teacher)
|
||||
|
||||
# The learner should learn about all nodes
|
||||
learner.learn_from_teacher_node()
|
||||
all_nodes = {teacher}
|
||||
all_nodes.update(other_nodes)
|
||||
assert learner not in all_nodes
|
||||
assert learner not in set(learner.known_nodes)
|
||||
assert set(learner.known_nodes) == all_nodes
|
||||
|
|
|
@ -1,41 +0,0 @@
|
|||
|
||||
|
||||
import maya
|
||||
import pytest
|
||||
import pytest_twisted as pt
|
||||
from twisted.internet.threads import deferToThread
|
||||
|
||||
|
||||
@pt.inlineCallbacks
|
||||
def test_one_node_stores_a_bunch_of_others(ursulas, lonely_ursula_maker):
|
||||
the_chosen_seednode = list(ursulas)[2] # ...neo?
|
||||
seed_node = the_chosen_seednode.seed_node_metadata()
|
||||
|
||||
newcomer = lonely_ursula_maker(
|
||||
quantity=1,
|
||||
save_metadata=True,
|
||||
seed_nodes=[seed_node]).pop()
|
||||
|
||||
assert not newcomer.known_nodes
|
||||
|
||||
newcomer.start_learning_loop(now=True)
|
||||
|
||||
def start_lonely_learning_loop():
|
||||
newcomer.start_learning_loop()
|
||||
start = maya.now()
|
||||
# Loop until the_chosen_seednode is in storage.
|
||||
while the_chosen_seednode.checksum_address not in [
|
||||
u.checksum_address for u in newcomer.node_storage.all()
|
||||
]:
|
||||
passed = maya.now() - start
|
||||
if passed.seconds > 2:
|
||||
pytest.fail("Didn't find the seed node.")
|
||||
|
||||
yield deferToThread(start_lonely_learning_loop)
|
||||
|
||||
matured_known_nodes = list(node.mature() for node in newcomer.known_nodes)
|
||||
assert list(matured_known_nodes)
|
||||
assert len(matured_known_nodes) == len(
|
||||
list(newcomer.node_storage.all())
|
||||
) # TODO: why are certificates note being stored here?
|
||||
assert set(matured_known_nodes) == set(list(newcomer.node_storage.all()))
|
|
@ -6,8 +6,6 @@ from nucypher_core.umbral import PublicKey
|
|||
from nucypher.network.server import make_rest_app
|
||||
from tests.mock.serials import good_serials
|
||||
|
||||
mock_cert_storage = patch("nucypher.config.storages.ForgetfulNodeStorage.store_node_certificate",
|
||||
new=lambda *args, **kwargs: "this_might_normally_be_a_filepath")
|
||||
mock_message_verification = patch('nucypher.characters.lawful.Alice.verify_from', new=lambda *args, **kwargs: None)
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue