mirror of https://github.com/nucypher/nucypher.git
use NodeStorage as the implcit default, removes storage confiuration abilities.
parent
2a6c6a2b35
commit
7ad2635b83
|
@ -572,4 +572,4 @@ class EthereumTesterClient(EthereumClient):
|
|||
return signature_and_stuff['signature']
|
||||
|
||||
def parse_transaction_data(self, transaction):
|
||||
return transaction.data # TODO: See https://github.com/ethereum/eth-tester/issues/173
|
||||
return transaction._certificates # TODO: See https://github.com/ethereum/eth-tester/issues/173
|
||||
|
|
|
@ -10,7 +10,6 @@ def confirm_destroy_configuration(config: CharacterConfiguration) -> bool:
|
|||
confirmation = CHARACTER_DESTRUCTION.format(name=config.NAME,
|
||||
root=config.config_root,
|
||||
keystore=config.keystore_dir,
|
||||
nodestore=config.node_storage.source,
|
||||
config=config.filepath)
|
||||
click.confirm(confirmation, abort=True)
|
||||
return True
|
||||
|
|
|
@ -30,7 +30,6 @@ SELECTED_ACCOUNT = "Selected {choice}: {chosen_account}"
|
|||
CHARACTER_DESTRUCTION = """
|
||||
Delete all {name} character files including:
|
||||
- Private and Public Keys ({keystore})
|
||||
- Known Nodes ({nodestore})
|
||||
- Node Configuration File ({config})
|
||||
|
||||
Are you sure?"""
|
||||
|
|
|
@ -559,8 +559,8 @@ class CharacterConfiguration(BaseConfiguration):
|
|||
def dev_mode(self) -> bool:
|
||||
return self.__dev_mode
|
||||
|
||||
def _setup_node_storage(self) -> None:
|
||||
node_storage = NodeStorage()
|
||||
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:
|
||||
|
@ -595,13 +595,12 @@ class CharacterConfiguration(BaseConfiguration):
|
|||
Warning: This method allows mutation and may result in an inconsistent configuration.
|
||||
"""
|
||||
payload = cls._read_configuration_file(filepath=filepath)
|
||||
node_storage = cls.load_node_storage(storage_payload=payload["node_storage"])
|
||||
max_gas_price = payload.get("max_gas_price") # gwei
|
||||
if max_gas_price:
|
||||
max_gas_price = Decimal(max_gas_price)
|
||||
|
||||
# Assemble
|
||||
payload.update(dict(node_storage=node_storage, max_gas_price=max_gas_price))
|
||||
payload.update(dict(max_gas_price=max_gas_price))
|
||||
payload = cast_paths_from(cls, payload)
|
||||
|
||||
# Filter out None values from **overrides to detect, well, overrides...
|
||||
|
@ -754,7 +753,6 @@ class CharacterConfiguration(BaseConfiguration):
|
|||
interactive=self.MNEMONIC_KEYSTORE)
|
||||
|
||||
self._cache_runtime_filepaths()
|
||||
self.node_storage.initialize()
|
||||
|
||||
# Validate
|
||||
if not self.__dev_mode:
|
||||
|
@ -785,10 +783,10 @@ class CharacterConfiguration(BaseConfiguration):
|
|||
@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.__subclasses__()}
|
||||
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.from_payload(payload=storage_payload)
|
||||
node_storage = storage_class()
|
||||
return node_storage
|
||||
|
||||
def configure_pre_payment_method(self):
|
||||
|
|
|
@ -1,16 +1,11 @@
|
|||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
from cryptography.hazmat.primitives._serialization import Encoding
|
||||
|
||||
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 = ':memory:'
|
||||
TLS_CERTIFICATE_ENCODING = Encoding.PEM
|
||||
_TYPE_LABEL = 'storage_type'
|
||||
_name = ':memory:'
|
||||
|
||||
class NodeStorageError(Exception):
|
||||
pass
|
||||
|
@ -19,7 +14,6 @@ class NodeStorage:
|
|||
pass
|
||||
|
||||
def __init__(self, character_class=None):
|
||||
self.__certificates = dict()
|
||||
self.__metadata = dict()
|
||||
from nucypher.characters.lawful import Ursula
|
||||
self.character_class = character_class or Ursula
|
||||
|
@ -29,15 +23,11 @@ class NodeStorage:
|
|||
return self.get(checksum_address=item)
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
return self.store_node_metadata(node=value)
|
||||
return self.set(node=value)
|
||||
|
||||
def __iter__(self):
|
||||
return self.all()
|
||||
|
||||
def initialize(self):
|
||||
self.__metadata = dict()
|
||||
self.__certificates = dict()
|
||||
|
||||
def all(self) -> set:
|
||||
return set(self.__metadata.values())
|
||||
|
||||
|
@ -51,17 +41,10 @@ class NodeStorage:
|
|||
except KeyError:
|
||||
raise self.UnknownNode
|
||||
|
||||
def store_node_certificate(self, host: str, certificate):
|
||||
self.__certificates[host] = certificate
|
||||
return self.__certificates[host]
|
||||
|
||||
def store_node_metadata(self, node) -> bytes:
|
||||
def set(self, node) -> bytes:
|
||||
self.__metadata[node.stamp] = node
|
||||
return self.__metadata[node.stamp]
|
||||
|
||||
def clear(self, metadata: bool = True, certificates: bool = True) -> None:
|
||||
def clear(self) -> None:
|
||||
"""Forget all stored nodes and certificates"""
|
||||
if metadata is True:
|
||||
self.__metadata = dict()
|
||||
if certificates is True:
|
||||
self.__certificates = dict()
|
||||
self.__metadata = dict()
|
||||
|
|
|
@ -45,6 +45,7 @@ class NucypherMiddlewareClient:
|
|||
|
||||
@staticmethod
|
||||
def response_cleaner(response):
|
||||
|
||||
return response
|
||||
|
||||
def verify_and_parse_node_or_host_and_port(self, node_or_sprout, host, port):
|
||||
|
@ -115,6 +116,7 @@ class NucypherMiddlewareClient:
|
|||
host=host,
|
||||
port=port,
|
||||
path="public_information",
|
||||
timeout=2
|
||||
)
|
||||
return response.content
|
||||
|
||||
|
|
|
@ -285,8 +285,7 @@ class Learner:
|
|||
self._learning_deferred = None
|
||||
self._discovery_canceller = DiscoveryCanceller()
|
||||
|
||||
if not node_storage:
|
||||
node_storage = self.__DEFAULT_NODE_STORAGE()
|
||||
node_storage = 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")
|
||||
|
@ -400,7 +399,7 @@ class Learner:
|
|||
return discovered
|
||||
|
||||
def read_nodes_from_storage(self) -> List:
|
||||
stored_nodes = self.node_storage.all() # TODO: #466
|
||||
stored_nodes = self.node_storage.all()
|
||||
|
||||
restored_from_disk = []
|
||||
invalid_nodes = defaultdict(list)
|
||||
|
@ -443,7 +442,7 @@ class Learner:
|
|||
self.known_nodes.record_node(node) # FIXME - dont always remember nodes, bucket them.
|
||||
|
||||
if self.save_metadata:
|
||||
self.node_storage.store_node_metadata(node=node)
|
||||
self.node_storage.set(node=node)
|
||||
|
||||
if eager:
|
||||
node.mature()
|
||||
|
@ -543,7 +542,8 @@ class Learner:
|
|||
nodes_we_know_about = self.known_nodes.shuffled()
|
||||
|
||||
if not nodes_we_know_about:
|
||||
raise self.NotEnoughTeachers("Need some nodes to start learning from.")
|
||||
self.log.warn("Need some nodes to start learning from.")
|
||||
return False
|
||||
|
||||
self.teacher_nodes.extend(nodes_we_know_about)
|
||||
|
||||
|
@ -554,7 +554,7 @@ class Learner:
|
|||
self._current_teacher_node = self.teacher_nodes.pop()
|
||||
except IndexError:
|
||||
error = "Not enough nodes to select a good teacher, Check your network connection then node configuration"
|
||||
raise self.NotEnoughTeachers(error)
|
||||
self.log.warn(error)
|
||||
self.log.debug("Cycled teachers; New teacher is {}".format(self._current_teacher_node))
|
||||
|
||||
def current_teacher_node(self, cycle=False):
|
||||
|
@ -728,7 +728,7 @@ class Learner:
|
|||
# Scenario 3: We don't know about this node, and neither does our friend.
|
||||
|
||||
def write_node_metadata(self, node, serializer=bytes) -> str:
|
||||
return self.node_storage.store_node_metadata(node=node)
|
||||
return self.node_storage.set(node=node)
|
||||
|
||||
def verify_from(
|
||||
self,
|
||||
|
@ -773,7 +773,9 @@ class Learner:
|
|||
|
||||
self._learning_round += 1
|
||||
|
||||
current_teacher = self.current_teacher_node() # Will raise if there's no available teacher.
|
||||
current_teacher = self.current_teacher_node()
|
||||
if not current_teacher:
|
||||
return RELAX
|
||||
current_teacher.mature()
|
||||
|
||||
if isinstance(self, Teacher):
|
||||
|
@ -799,12 +801,9 @@ class Learner:
|
|||
self.log.info(f"Teacher {current_teacher.seed_node_metadata(as_teacher_uri=True)} is unreachable: {e}.")
|
||||
return
|
||||
except current_teacher.InvalidNode as e:
|
||||
# Ugh. The teacher is invalid. Rough.
|
||||
# TODO: Bucket separately and report.
|
||||
unresponsive_nodes.add(current_teacher) # This does nothing.
|
||||
# self.known_nodes.mark_as(current_teacher.InvalidNode, current_teacher)
|
||||
self.log.warn(f"Teacher {str(current_teacher)} is invalid: {e}.")
|
||||
# TODO (#567): bucket the node as suspicious
|
||||
unresponsive_nodes.add(current_teacher) # This does nothing.
|
||||
self.log.warn(f"Teacher {str(current_teacher)} is invalid: {e}.")
|
||||
return
|
||||
except RuntimeError as e:
|
||||
if canceller and canceller.stop_now:
|
||||
|
@ -822,7 +821,7 @@ class Learner:
|
|||
f"(hex={bytes(current_teacher.metadata()).hex()}):{e}.") # To track down 2345 / 1698
|
||||
raise
|
||||
finally:
|
||||
# Is cycling happening in the right order?
|
||||
# cycle now -- cycle even if this function raises an exception or returns early.
|
||||
self.cycle_teacher_node()
|
||||
|
||||
if response.status_code != 200:
|
||||
|
@ -836,9 +835,10 @@ class Learner:
|
|||
# TODO: we really should be checking this *before* we ask it for a node list,
|
||||
# but currently we may not know this before the REST request (which may mature the node)
|
||||
if self.domain != current_teacher.domain:
|
||||
# Ignore nodes from other domains.
|
||||
self.log.debug(f"{current_teacher} is serving '{current_teacher.domain}', "
|
||||
f"ignore since we are learning about '{self.domain}'")
|
||||
return # This node is not serving our domain.
|
||||
return
|
||||
|
||||
#
|
||||
# Deserialize
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import datetime
|
||||
import os
|
||||
import time
|
||||
|
||||
import maya
|
||||
import pytest
|
||||
import time
|
||||
from twisted.internet.task import Clock
|
||||
|
||||
from nucypher.characters.lawful import Bob, Enrico
|
||||
|
@ -38,7 +38,7 @@ def test_bob_full_retrieve_flow(
|
|||
assert b"Welcome to flippering number 0." == delivered_cleartexts[0]
|
||||
|
||||
|
||||
def test_bob_retrieves(alice, ursulas, certificates_tempdir):
|
||||
def test_bob_retrieves(alice, ursulas):
|
||||
"""A test to show that Bob can retrieve data from Ursula"""
|
||||
|
||||
# Let's partition Ursulas in two parts
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
import json
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
from constant_sorrow.constants import CERTIFICATE_NOT_SAVED, NO_KEYSTORE_ATTACHED
|
||||
from constant_sorrow.constants import NO_KEYSTORE_ATTACHED
|
||||
from nucypher_core.umbral import SecretKey
|
||||
from pathlib import Path
|
||||
|
||||
from nucypher.characters.lawful import Alice, Bob, Ursula
|
||||
from nucypher.cli.actions.configure import destroy_configuration
|
||||
|
@ -15,7 +14,7 @@ from nucypher.config.characters import (
|
|||
UrsulaConfiguration,
|
||||
)
|
||||
from nucypher.config.constants import TEMPORARY_DOMAIN_NAME
|
||||
from nucypher.config.storages import ForgetfulNodeStorage
|
||||
from nucypher.config.storages import NodeStorage
|
||||
from nucypher.crypto.keystore import Keystore
|
||||
from tests.constants import (
|
||||
INSECURE_DEVELOPMENT_PASSWORD,
|
||||
|
@ -77,7 +76,7 @@ def test_development_character_configurations(
|
|||
assert TEMPORARY_DOMAIN_NAME == str(thing_one.domain)
|
||||
|
||||
# Node Storage
|
||||
assert isinstance(thing_one.node_storage, ForgetfulNodeStorage)
|
||||
assert isinstance(thing_one.node_storage, NodeStorage)
|
||||
assert ":memory:" in thing_one.node_storage._name
|
||||
|
||||
# All development characters are unique
|
||||
|
@ -188,8 +187,7 @@ def test_ursula_development_configuration(testerchain):
|
|||
# A Temporary Ursula
|
||||
port = ursula_one.rest_information()[0].port
|
||||
assert port == UrsulaConfiguration.DEFAULT_DEVELOPMENT_REST_PORT
|
||||
assert ursula_one.certificate_filepath is CERTIFICATE_NOT_SAVED
|
||||
assert isinstance(ursula_one.node_storage, ForgetfulNodeStorage)
|
||||
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
|
||||
|
|
|
@ -3,7 +3,6 @@ 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.config.storages import ForgetfulNodeStorage, TemporaryFileBasedNodeStorage
|
||||
from nucypher.policy.payment import SubscriptionManagerPayment
|
||||
from nucypher.utilities.networking import LOOPBACK_ADDRESS
|
||||
from tests.constants import MOCK_ETH_PROVIDER_URI, TESTERCHAIN_CHAIN_ID
|
||||
|
@ -28,7 +27,7 @@ class BaseTestNodeStorageBackends:
|
|||
|
||||
def _read_and_write_metadata(self, ursula, node_storage, operator_addresses, signer):
|
||||
# Write Node
|
||||
node_storage.store_node_metadata(node=ursula)
|
||||
node_storage.set(node=ursula)
|
||||
|
||||
# Read Node
|
||||
node_from_storage = node_storage.get(stamp=ursula.stamp)
|
||||
|
@ -55,7 +54,7 @@ class BaseTestNodeStorageBackends:
|
|||
TESTERCHAIN_CHAIN_ID: [MOCK_ETH_PROVIDER_URI]
|
||||
},
|
||||
)
|
||||
node_storage.store_node_metadata(node=node)
|
||||
node_storage.set(node=node)
|
||||
all_known_nodes.add(node)
|
||||
|
||||
# Read all nodes from storage
|
||||
|
@ -87,39 +86,3 @@ class BaseTestNodeStorageBackends:
|
|||
operator_addresses=testerchain.ursulas_accounts,
|
||||
signer=Web3Signer(testerchain.client))
|
||||
self.storage_backend.clear()
|
||||
|
||||
|
||||
class TestInMemoryNodeStorage(BaseTestNodeStorageBackends):
|
||||
storage_backend = ForgetfulNodeStorage(character_class=BaseTestNodeStorageBackends.character_class)
|
||||
storage_backend.initialize()
|
||||
|
||||
|
||||
class TestTemporaryFileBasedNodeStorage(BaseTestNodeStorageBackends):
|
||||
storage_backend = TemporaryFileBasedNodeStorage(character_class=BaseTestNodeStorageBackends.character_class)
|
||||
storage_backend.initialize()
|
||||
|
||||
def test_invalid_metadata(self, light_ursula, testerchain):
|
||||
self._read_and_write_metadata(ursula=light_ursula, node_storage=self.storage_backend, operator_addresses=testerchain.ursulas_accounts, signer=Web3Signer(testerchain.client))
|
||||
some_node, another_node, *other = list(self.storage_backend.metadata_dir.iterdir())
|
||||
|
||||
# Let's break the metadata (but not the version)
|
||||
metadata_path = self.storage_backend.metadata_dir / some_node
|
||||
with open(metadata_path, 'wb') as file:
|
||||
file.write(make_header(b'NdMd', 1, 0) + b'invalid')
|
||||
|
||||
with pytest.raises(TemporaryFileBasedNodeStorage.InvalidNodeMetadata):
|
||||
self.storage_backend.get(stamp=some_node.name[:-5], certificate_only=False)
|
||||
|
||||
# Let's break the metadata, by putting a completely wrong version
|
||||
metadata_path = self.storage_backend.metadata_dir / another_node
|
||||
with open(metadata_path, 'wb') as file:
|
||||
full_header = make_header(b'NdMd', 1, 0)
|
||||
file.write(full_header[:-1]) # Not even a valid header
|
||||
|
||||
with pytest.raises(TemporaryFileBasedNodeStorage.InvalidNodeMetadata):
|
||||
self.storage_backend.get(stamp=another_node.name[:-5], 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(certificates_only=False)
|
||||
total_nodes = 1 + ADDITIONAL_NODES_TO_LEARN_ABOUT
|
||||
assert total_nodes - 2 == len(restored_nodes)
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import contextlib
|
||||
import time
|
||||
|
||||
import maya
|
||||
import pytest
|
||||
import time
|
||||
from nucypher_core.umbral import SecretKey, Signer
|
||||
|
||||
from nucypher.characters.lawful import Ursula
|
||||
|
@ -10,7 +10,6 @@ from nucypher.crypto.signing import SignatureStamp
|
|||
from tests.mock.performance_mocks import (
|
||||
VerificationTracker,
|
||||
mock_cert_loading,
|
||||
mock_cert_storage,
|
||||
mock_message_verification,
|
||||
mock_metadata_validation,
|
||||
mock_secret_source,
|
||||
|
@ -57,7 +56,7 @@ def test_alice_can_learn_about_a_whole_bunch_of_ursulas(highperf_mocked_alice):
|
|||
_teacher_known_nodes_bytestring = actual_ursula.bytestring_of_known_nodes()
|
||||
actual_ursula.bytestring_of_known_nodes = lambda *args, **kwargs: _teacher_known_nodes_bytestring # TODO: Formalize this? #1537
|
||||
|
||||
with mock_cert_storage, mock_cert_loading, mock_verify_node, mock_message_verification, mock_metadata_validation:
|
||||
with mock_cert_loading, mock_verify_node, mock_message_verification, mock_metadata_validation:
|
||||
started = time.time()
|
||||
highperf_mocked_alice.block_until_number_of_known_nodes_is(4000, learn_on_this_thread=True)
|
||||
ended = time.time()
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
import tests
|
||||
|
@ -7,7 +5,7 @@ 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.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
|
||||
|
@ -114,11 +112,7 @@ def test_learner_restores_metadata_from_storage(
|
|||
):
|
||||
# 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))
|
||||
old_storage = NodeStorage()
|
||||
|
||||
# Use the ursula maker with this storage so it's populated with nodes from one domain
|
||||
_some_ursulas = lonely_ursula_maker(
|
||||
|
@ -231,11 +225,7 @@ def test_learner_uses_both_nodes_from_storage_and_fallback_nodes(
|
|||
|
||||
# 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))
|
||||
node_storage = NodeStorage()
|
||||
|
||||
# Create some nodes and persist them to local storage
|
||||
other_nodes = make_ursulas(
|
||||
|
|
|
@ -31,7 +31,7 @@ def test_middleware_response_status_code_processing(
|
|||
_original_execute_method = mock_rest_middleware.client._execute_method
|
||||
|
||||
def execute_method_side_effect(
|
||||
node_or_sprout, host, port, method, endpoint, *args, **kwargs
|
||||
method, endpoint, *args, **kwargs
|
||||
):
|
||||
endpoint_url = urlparse(endpoint)
|
||||
if endpoint_url.path == "/reencrypt":
|
||||
|
@ -42,7 +42,7 @@ def test_middleware_response_status_code_processing(
|
|||
return response
|
||||
else:
|
||||
return _original_execute_method(
|
||||
node_or_sprout, host, port, method, endpoint, *args, **kwargs
|
||||
method, endpoint, *args, **kwargs
|
||||
)
|
||||
|
||||
mocker.patch.object(
|
||||
|
|
|
@ -111,8 +111,6 @@ def test_alice_refuses_to_select_node_unless_ursula_is_valid(
|
|||
substitute_verifying_key=True,
|
||||
sign_metadata=True)
|
||||
|
||||
vladimir.node_storage.store_node_certificate(certificate=target.certificate, port=vladimir.rest_interface.port)
|
||||
|
||||
# Ideally, a fishy node will be present in `known_nodes`,
|
||||
# This tests the case when it became fishy after discovering it
|
||||
# but before being selected for a policy.
|
||||
|
|
|
@ -18,9 +18,9 @@ def test_ursula_html_renders(ursula, client):
|
|||
assert response.status_code == 404
|
||||
response = client.get('/status/')
|
||||
assert response.status_code == 200
|
||||
assert b'<!DOCTYPE html>' in response.data
|
||||
assert ursula.checksum_address.encode() in response.data
|
||||
assert str(ursula.nickname).encode() in response.data
|
||||
assert b'<!DOCTYPE html>' in response._certificates
|
||||
assert ursula.checksum_address.encode() in response._certificates
|
||||
assert str(ursula.nickname).encode() in response._certificates
|
||||
|
||||
|
||||
@pytest.mark.parametrize('omit_known_nodes', [False, True])
|
||||
|
|
Loading…
Reference in New Issue