nucypher/tests/integration/learning/test_domains.py

268 lines
9.2 KiB
Python

import pytest
from pathlib import Path
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 LocalFileBasedNodeStorage
from nucypher.network.nodes import TEACHER_NODES
from tests.constants import TESTERCHAIN_CHAIN_INFO, TEMPORARY_DOMAIN
from tests.utils.registry import MockRegistrySource
from tests.utils.ursula import make_ursulas
@pytest.fixture(scope="module")
def domain_1():
return TACoDomain(
name="domain_uno",
eth_chain=TESTERCHAIN_CHAIN_INFO,
polygon_chain=TESTERCHAIN_CHAIN_INFO,
)
@pytest.fixture(scope="module")
def domain_2():
return TACoDomain(
name="domain_dos",
eth_chain=TESTERCHAIN_CHAIN_INFO,
polygon_chain=TESTERCHAIN_CHAIN_INFO,
)
@pytest.fixture(scope="module")
def test_registry(module_mocker, domain_1, domain_2):
with tests.utils.registry.mock_registry_sources(
mocker=module_mocker, _domains=[domain_1, domain_2, TEMPORARY_DOMAIN]
):
# doesn't really matter what domain is used here
registry = ContractRegistry(MockRegistrySource(domain=domain_1))
yield registry
@pytest.fixture(scope="module")
def registry_1(domain_1, test_registry):
return ContractRegistry(MockRegistrySource(domain=domain_1))
@pytest.fixture(scope="module")
def registry_2(domain_2, test_registry):
return ContractRegistry(MockRegistrySource(domain=domain_2))
@pytest.mark.skip("inconsistent behaviour on CI - see #3289")
def test_learner_learns_about_domains_separately(
lonely_ursula_maker, domain_1, domain_2, registry_1, registry_2, caplog
):
hero_learner, other_first_domain_learner = lonely_ursula_maker(
domain=domain_1,
registry=registry_1,
quantity=2,
)
_nobody = lonely_ursula_maker(
domain=domain_1, registry=registry_1, quantity=1
).pop()
other_first_domain_learner.remember_node(_nobody, eager=True)
second_domain_learners = lonely_ursula_maker(
domain=domain_2, registry=registry_2, know_each_other=True, quantity=3
)
assert len(hero_learner.known_nodes) == 0
# Learn from a teacher in our domain.
hero_learner.remember_node(other_first_domain_learner, eager=True)
hero_learner.start_learning_loop(now=True)
hero_learner.learn_from_teacher_node(eager=True)
# All domain 1 nodes
assert len(hero_learner.known_nodes) == 2
# Learn about the second domain.
hero_learner._current_teacher_node = second_domain_learners.pop()
hero_learner.learn_from_teacher_node(eager=True)
# All domain 1 nodes
assert len(hero_learner.known_nodes) == 2
new_first_domain_learner = lonely_ursula_maker(
domain=domain_1, registry=registry_1, quantity=1
).pop()
_new_second_domain_learner = lonely_ursula_maker(
domain=domain_2, registry=registry_2, quantity=1
).pop()
new_first_domain_learner.remember_node(hero_learner, eager=True)
new_first_domain_learner.learn_from_teacher_node(eager=True)
# 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 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
root = tmpdir.mkdir("known_nodes")
metadata = root.mkdir("metadata")
certs = root.mkdir("certs")
old_storage = LocalFileBasedNodeStorage(metadata_dir=Path(metadata),
certificates_dir=Path(certs),
storage_root=Path(root))
# 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
root = tmpdir.mkdir("known_nodes")
metadata = root.mkdir("metadata")
certs = root.mkdir("certs")
node_storage = LocalFileBasedNodeStorage(metadata_dir=Path(metadata),
certificates_dir=Path(certs),
storage_root=Path(root))
# 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 set(learner.known_nodes) == all_nodes