diff --git a/deploy/docker/porter/Dockerfile b/deploy/docker/porter/Dockerfile
index 477ff7354..369a07a42 100644
--- a/deploy/docker/porter/Dockerfile
+++ b/deploy/docker/porter/Dockerfile
@@ -1,8 +1,8 @@
-FROM python:3.8.7-slim
+FROM python:3.8.12-slim
# Update
-RUN apt update -y && apt upgrade -y
-RUN apt install patch gcc libffi-dev wget git -y
+RUN apt-get update -y && apt upgrade -y
+RUN apt-get install patch gcc libffi-dev wget git -y
WORKDIR /code
COPY . /code
diff --git a/examples/finnegans_wake_demo/finnegans-wake-demo-federated.py b/examples/finnegans_wake_demo/finnegans-wake-demo-federated.py
index 0cb5117d1..466bb0c0b 100644
--- a/examples/finnegans_wake_demo/finnegans-wake-demo-federated.py
+++ b/examples/finnegans_wake_demo/finnegans-wake-demo-federated.py
@@ -36,7 +36,7 @@ from nucypher.utilities.logging import GlobalLoggerSettings
BOOK_PATH = Path(os.getenv('FINNEGANS_WAKE_PATH') or 'finnegans-wake-excerpt.txt')
# Twisted Logger
-GlobalLoggerSettings.set_log_level(log_level_name='debug')
+GlobalLoggerSettings.set_log_level(log_level_name='info')
GlobalLoggerSettings.start_console_logging()
# if your ursulas are NOT running on your current host,
diff --git a/examples/finnegans_wake_demo/finnegans-wake-demo-testnet-l2.py b/examples/finnegans_wake_demo/finnegans-wake-demo-testnet-l2.py
index 7dbfb6191..ee9ddd741 100644
--- a/examples/finnegans_wake_demo/finnegans-wake-demo-testnet-l2.py
+++ b/examples/finnegans_wake_demo/finnegans-wake-demo-testnet-l2.py
@@ -16,27 +16,26 @@
"""
-from getpass import getpass
-
import datetime
-import maya
import os
+from getpass import getpass
from pathlib import Path
-from web3.main import Web3
+
+import maya
from nucypher.blockchain.eth.signers.base import Signer
-from nucypher.characters.lawful import Alice, Bob, Ursula
+from nucypher.characters.lawful import Alice, Bob
from nucypher.characters.lawful import Enrico as Enrico
from nucypher.crypto.powers import SigningPower, DecryptingPower
+from nucypher.policy.payment import SubscriptionManagerPayment
from nucypher.utilities.ethereum import connect_web3_provider
from nucypher.utilities.logging import GlobalLoggerSettings
-
######################
# Boring setup stuff #
######################
-GlobalLoggerSettings.set_log_level(log_level_name='debug')
+GlobalLoggerSettings.set_log_level(log_level_name='info')
GlobalLoggerSettings.start_console_logging()
BOOK_PATH = Path('finnegans-wake-excerpt.txt')
@@ -62,9 +61,10 @@ except KeyError:
# NuCypher Network #
####################
-L1_TESTNET = 'lynx'
+L1_TESTNET = 'ibex'
L2_TESTNET = 'mumbai' # TODO: Needs name different than the network name
+
#####################
# Bob the BUIDLer ##
#####################
@@ -92,18 +92,23 @@ wallet = Signer.from_signer_uri(SIGNER_URI)
password = os.environ.get('DEMO_ALICE_PASSWORD') or getpass(f"Enter password to unlock {ALICE_ADDRESS[:8]}: ")
wallet.unlock_account(account=ALICE_ADDRESS, password=password)
+
+payment_method = SubscriptionManagerPayment(
+ network='mumbai',
+ eth_provider=L2_PROVIDER
+)
+
# This is Alice.
alice = Alice(
checksum_address=ALICE_ADDRESS,
signer=wallet,
domain=L1_TESTNET,
- payment_network=L2_TESTNET,
- payment_provider=L2_PROVIDER,
eth_provider_uri=L1_PROVIDER,
+ payment_method=payment_method
)
# Alice puts her public key somewhere for Bob to find later...
-alice_verifying_key = bytes(alice.stamp)
+alice_verifying_key = alice.stamp.as_umbral_pubkey()
# Alice can get the policy's public key even before creating the policy.
label = b"secret/files/42"
@@ -114,17 +119,26 @@ policy_public_key = alice.get_policy_encrypting_key_from_label(label)
# can be shared with any Bob that Alice grants access.
# Alice already knows Bob's public keys from a side-channel.
-remote_bob = Bob.from_public_keys(encrypting_key=encrypting_key, verifying_key=verifying_key)
+remote_bob = Bob.from_public_keys(
+ encrypting_key=encrypting_key,
+ verifying_key=verifying_key,
+)
# These are the policy details for bob.
# In this example bob will be granted access for 1 day,
# trusting 2 of 3 nodes paying each of them 50 gwei per period.
expiration = maya.now() + datetime.timedelta(days=1)
threshold, shares = 2, 3
-price = alice.payment_method.quote(expiration=expiration, shares=shares).value
+price = alice.payment_method.quote(expiration=expiration.epoch, shares=shares).value
# Alice grants access to Bob...
-policy = alice.grant(remote_bob, label, threshold=threshold, shares=shares, value=price, expiration=expiration)
+policy = alice.grant(
+ remote_bob, label,
+ threshold=threshold,
+ shares=shares,
+ value=price,
+ expiration=expiration,
+)
# ...and then disappears from the internet.
#
@@ -169,5 +183,6 @@ for counter, plaintext in enumerate(finnegans_wake):
# We show that indeed this is the passage originally encrypted by Enrico.
assert plaintext == cleartexts[0]
+ print(cleartexts)
bob.disenchant()
diff --git a/examples/finnegans_wake_demo/finnegans-wake-demo-testnet.py b/examples/finnegans_wake_demo/finnegans-wake-demo-testnet.py
index fde0f998f..78a013fa6 100644
--- a/examples/finnegans_wake_demo/finnegans-wake-demo-testnet.py
+++ b/examples/finnegans_wake_demo/finnegans-wake-demo-testnet.py
@@ -36,7 +36,7 @@ from nucypher.utilities.logging import GlobalLoggerSettings
# Boring setup stuff #
######################
-GlobalLoggerSettings.set_log_level(log_level_name='debug')
+GlobalLoggerSettings.set_log_level(log_level_name='info')
GlobalLoggerSettings.start_console_logging()
BOOK_PATH = Path('finnegans-wake-excerpt.txt')
diff --git a/newsfragments/2873.feature.rst b/newsfragments/2873.feature.rst
new file mode 100644
index 000000000..e4df35ee9
--- /dev/null
+++ b/newsfragments/2873.feature.rst
@@ -0,0 +1,3 @@
+- Full support of policy payments sumitted to polygon in demos and top-level APIs.
+- Improved certificate handling for network requests.
+- Prioritizes in-memory node storage for all node runtimes.
diff --git a/newsfragments/2873.misc.rst b/newsfragments/2873.misc.rst
new file mode 100644
index 000000000..f62e8403a
--- /dev/null
+++ b/newsfragments/2873.misc.rst
@@ -0,0 +1,2 @@
+Extend policy probationary period to 2022-6-16T23:59:59.0Z.
+
diff --git a/nucypher/blockchain/eth/actors.py b/nucypher/blockchain/eth/actors.py
index 72c050cff..4a22f6356 100644
--- a/nucypher/blockchain/eth/actors.py
+++ b/nucypher/blockchain/eth/actors.py
@@ -383,9 +383,13 @@ class Operator(BaseActor):
class BlockchainPolicyAuthor(NucypherTokenActor):
"""Alice base class for blockchain operations, mocking up new policies!"""
- def __init__(self, *args, **kwargs):
+ def __init__(self, eth_provider_uri: str, *args, **kwargs):
super().__init__(*args, **kwargs)
- self.application_agent = ContractAgency.get_agent(PREApplicationAgent, registry=self.registry)
+ self.application_agent = ContractAgency.get_agent(
+ PREApplicationAgent,
+ registry=self.registry,
+ eth_provider_uri=eth_provider_uri
+ )
def create_policy(self, *args, **kwargs):
"""Hence the name, a BlockchainPolicyAuthor can create a BlockchainPolicy with themself as the author."""
diff --git a/nucypher/blockchain/eth/agents.py b/nucypher/blockchain/eth/agents.py
index 790b7844b..de3cf567e 100644
--- a/nucypher/blockchain/eth/agents.py
+++ b/nucypher/blockchain/eth/agents.py
@@ -92,7 +92,7 @@ class EthereumContractAgent:
contract_version: Optional[str] = None):
self.log = Logger(self.__class__.__name__)
- self.registry_str = str(registry)
+ self.registry = registry
self.blockchain = BlockchainInterfaceFactory.get_or_create_interface(eth_provider_uri=eth_provider_uri)
@@ -111,15 +111,17 @@ class EthereumContractAgent:
transaction_gas = EthereumContractAgent.DEFAULT_TRANSACTION_GAS_LIMITS['default']
self.transaction_gas = transaction_gas
- self.log.info("Initialized new {} for {} with {} and {}".format(self.__class__.__name__,
- self.contract.address,
- self.blockchain.eth_provider_uri,
- self.registry_str))
+ self.log.info("Initialized new {} for {} with {} and {}".format(
+ self.__class__.__name__,
+ self.contract.address,
+ self.blockchain.eth_provider_uri,
+ str(self.registry)
+ ))
def __repr__(self) -> str:
class_name = self.__class__.__name__
r = "{}(registry={}, contract={})"
- return r.format(class_name, self.registry_str, self.contract_name)
+ return r.format(class_name, str(self.registry), self.contract_name)
def __eq__(self, other: Any) -> bool:
return bool(self.contract.address == other.contract.address)
diff --git a/nucypher/characters/lawful.py b/nucypher/characters/lawful.py
index 565c91655..5dbc76037 100644
--- a/nucypher/characters/lawful.py
+++ b/nucypher/characters/lawful.py
@@ -71,7 +71,7 @@ from nucypher.characters.banners import ALICE_BANNER, BOB_BANNER, ENRICO_BANNER,
from nucypher.characters.base import Character, Learner
from nucypher.characters.control.interfaces import AliceInterface, BobInterface, EnricoInterface
from nucypher.cli.processes import UrsulaCommandProtocol
-from nucypher.config.storages import ForgetfulNodeStorage, NodeStorage
+from nucypher.config.storages import NodeStorage
from nucypher.control.controllers import WebController
from nucypher.control.emitters import StdoutEmitter
from nucypher.crypto.keypairs import HostingKeypair
@@ -169,7 +169,8 @@ class Alice(Character, BlockchainPolicyAuthor):
BlockchainPolicyAuthor.__init__(self,
domain=self.domain,
transacting_power=self.transacting_power,
- registry=self.registry)
+ registry=self.registry,
+ eth_provider_uri=eth_provider_uri)
self.log = Logger(self.__class__.__name__)
if is_me:
@@ -572,6 +573,7 @@ class Bob(Character):
if not publisher_verifying_key:
publisher_verifying_key = alice_verifying_key
+ publisher_verifying_key = PublicKey.from_bytes(bytes(publisher_verifying_key))
# A small optimization to avoid multiple treasure map decryptions.
map_hash = hash(bytes(encrypted_treasure_map))
@@ -715,7 +717,7 @@ class Ursula(Teacher, Character, Operator):
known_nodes: Iterable[Teacher] = None,
**character_kwargs
- ) -> None:
+ ):
Character.__init__(self,
is_me=is_me,
@@ -1075,15 +1077,9 @@ class Ursula(Teacher, Character, Operator):
def from_rest_url(cls,
network_middleware: RestMiddleware,
host: str,
- port: int,
- certificate_filepath,
- *args, **kwargs
- ):
- response_data = network_middleware.client.node_information(host, port,
- certificate_filepath=certificate_filepath)
-
+ port: int):
+ response_data = network_middleware.client.node_information(host, port)
stranger_ursula_from_public_keys = cls.from_metadata_bytes(response_data)
-
return stranger_ursula_from_public_keys
@classmethod
@@ -1134,7 +1130,7 @@ class Ursula(Teacher, Character, Operator):
except NodeSeemsToBeDown as e:
log = Logger(cls.__name__)
log.warn(
- "Can't connect to seed node (attempt {}). Will retry in {} seconds.".format(attempt, interval))
+ "Can't connect to peer (attempt {}). Will retry in {} seconds.".format(attempt, interval))
time.sleep(interval)
return __attempt(attempt=attempt + 1)
else:
@@ -1149,8 +1145,6 @@ class Ursula(Teacher, Character, Operator):
minimum_stake: int = 0,
registry: BaseContractRegistry = None,
network_middleware: RestMiddleware = None,
- *args,
- **kwargs
) -> Union['Ursula', 'NodeSprout']:
if network_middleware is None:
@@ -1161,25 +1155,18 @@ class Ursula(Teacher, Character, Operator):
# Fetch the hosts TLS certificate and read the common name
try:
- certificate = network_middleware.get_certificate(host=host, port=port)
+ certificate, _filepath = network_middleware.client.get_certificate(host=host, port=port)
except NodeSeemsToBeDown as e:
e.args += (f"While trying to load seednode {seed_uri}",)
e.crash_right_now = True
raise
real_host = certificate.subject.get_attributes_for_oid(NameOID.COMMON_NAME)[0].value
- # Create a temporary certificate storage area
- temp_node_storage = ForgetfulNodeStorage(federated_only=federated_only)
- temp_certificate_filepath = temp_node_storage.store_node_certificate(certificate=certificate, port=port)
-
# Load the host as a potential seed node
potential_seed_node = cls.from_rest_url(
host=real_host,
port=port,
network_middleware=network_middleware,
- certificate_filepath=temp_certificate_filepath,
- *args,
- **kwargs
)
# Check the node's stake (optional)
@@ -1189,8 +1176,6 @@ class Ursula(Teacher, Character, Operator):
if seednode_stake < minimum_stake:
raise Learner.NotATeacher(f"{staking_provider_address} is staking less than the specified minimum stake value ({minimum_stake}).")
- # OK - everyone get out
- temp_node_storage.forget()
return potential_seed_node
@classmethod
diff --git a/nucypher/cli/commands/ursula.py b/nucypher/cli/commands/ursula.py
index bcd741df4..ce364b2d9 100644
--- a/nucypher/cli/commands/ursula.py
+++ b/nucypher/cli/commands/ursula.py
@@ -14,6 +14,8 @@
You should have received a copy of the GNU Affero General Public License
along with nucypher. If not, see .
"""
+
+
from pathlib import Path
import click
@@ -54,7 +56,11 @@ from nucypher.cli.options import (
option_teacher_uri,
option_lonely,
option_max_gas_price,
- option_key_material, option_payment_provider, option_payment_method, option_payment_network
+ option_key_material,
+ option_payment_provider,
+ option_payment_method,
+ option_payment_network,
+ option_policy_registry_filepath
)
from nucypher.cli.painting.help import paint_new_installation_help
from nucypher.cli.types import EIP55_CHECKSUM_ADDRESS, NETWORK_PORT, OPERATOR_IP
@@ -80,6 +86,7 @@ class UrsulaConfigOptions:
db_filepath: Path,
network: str,
registry_filepath: Path,
+ policy_registry_filepath: Path,
dev: bool,
poa: bool,
light: bool,
@@ -94,9 +101,9 @@ class UrsulaConfigOptions:
):
if federated_only:
- if registry_filepath:
+ if registry_filepath or policy_registry_filepath:
raise click.BadOptionUsage(option_name="--registry-filepath",
- message=f"--registry-filepath cannot be used in federated mode.")
+ message=f"--registry-filepath and --policy-registry-filepath cannot be used in federated mode.")
self.eth_provider_uri = eth_provider_uri
self.signer_uri = signer_uri
@@ -107,6 +114,7 @@ class UrsulaConfigOptions:
self.db_filepath = db_filepath
self.domain = network
self.registry_filepath = registry_filepath
+ self.policy_registry_filepath = policy_registry_filepath
self.dev = dev
self.poa = poa
self.light = light
@@ -127,6 +135,7 @@ class UrsulaConfigOptions:
poa=self.poa,
light=self.light,
registry_filepath=self.registry_filepath,
+ policy_registry_filepath=self.policy_registry_filepath,
eth_provider_uri=self.eth_provider_uri,
signer_uri=self.signer_uri,
gas_strategy=self.gas_strategy,
@@ -152,6 +161,7 @@ class UrsulaConfigOptions:
filepath=config_file,
domain=self.domain,
registry_filepath=self.registry_filepath,
+ policy_registry_filepath=self.policy_registry_filepath,
eth_provider_uri=self.eth_provider_uri,
signer_uri=self.signer_uri,
gas_strategy=self.gas_strategy,
@@ -200,6 +210,7 @@ class UrsulaConfigOptions:
federated_only=self.federated_only,
operator_address=self.operator_address,
registry_filepath=self.registry_filepath,
+ policy_registry_filepath=self.policy_registry_filepath,
eth_provider_uri=self.eth_provider_uri,
signer_uri=self.signer_uri,
gas_strategy=self.gas_strategy,
@@ -220,6 +231,7 @@ class UrsulaConfigOptions:
federated_only=self.federated_only,
operator_address=self.operator_address,
registry_filepath=self.registry_filepath,
+ policy_registry_filepath=self.policy_registry_filepath,
eth_provider_uri=self.eth_provider_uri,
signer_uri=self.signer_uri,
gas_strategy=self.gas_strategy,
@@ -250,6 +262,7 @@ group_config_options = group_options(
db_filepath=option_db_filepath,
network=option_network(),
registry_filepath=option_registry_filepath,
+ policy_registry_filepath=option_policy_registry_filepath,
poa=option_poa,
light=option_light,
dev=option_dev,
diff --git a/nucypher/cli/options.py b/nucypher/cli/options.py
index 9eaa0d52f..1511db439 100644
--- a/nucypher/cli/options.py
+++ b/nucypher/cli/options.py
@@ -63,6 +63,7 @@ option_payment_network = click.option('--payment-network', help="Payment network
option_payment_method = click.option('--payment-method', help="Payment method name", type=PAYMENT_METHOD_CHOICES, required=False)
option_poa = click.option('--poa/--disable-poa', help="Inject POA middleware", is_flag=True, default=None)
option_registry_filepath = click.option('--registry-filepath', help="Custom contract registry filepath", type=EXISTING_READABLE_FILE)
+option_policy_registry_filepath = click.option('--policy-registry-filepath', help="Custom contract registry filepath for policies", type=EXISTING_READABLE_FILE)
option_shares = click.option('--shares', '-n', help="N-Total shares", type=click.INT)
option_signer_uri = click.option('--signer', 'signer_uri', '-S', default=None, type=str)
option_staking_provider = click.option('--staking-provider', help="Staking provider ethereum address", type=EIP55_CHECKSUM_ADDRESS, required=True)
diff --git a/nucypher/cli/utils.py b/nucypher/cli/utils.py
index 75fa7c01d..2a87866d4 100644
--- a/nucypher/cli/utils.py
+++ b/nucypher/cli/utils.py
@@ -123,7 +123,7 @@ def make_cli_character(character_config,
if character_config.federated_only:
emitter.message(FEDERATED_WARNING, color='yellow')
- emitter.message(f"Loaded {CHARACTER.__class__.__name__} {CHARACTER.checksum_address} ({CHARACTER.domain})", color='green')
+ emitter.message(f"Loaded {CHARACTER.__class__.__name__} ({CHARACTER.domain})", color='green')
return CHARACTER
diff --git a/nucypher/config/base.py b/nucypher/config/base.py
index 7fb00201c..b6f305174 100644
--- a/nucypher/config/base.py
+++ b/nucypher/config/base.py
@@ -408,9 +408,11 @@ class CharacterConfiguration(BaseConfiguration):
payment_provider: str = None,
payment_network: str = None,
- # Registry
+ # Registries
registry: BaseContractRegistry = None,
registry_filepath: Optional[Path] = None,
+ policy_registry: BaseContractRegistry = None,
+ policy_registry_filepath: Optional[Path] = None,
# Deployed Operators
worker_data: dict = None
@@ -444,6 +446,9 @@ class CharacterConfiguration(BaseConfiguration):
self.registry = registry or NO_BLOCKCHAIN_CONNECTION.bool_value(False)
self.registry_filepath = registry_filepath or UNINITIALIZED_CONFIGURATION
+ self.policy_registry = policy_registry or NO_BLOCKCHAIN_CONNECTION.bool_value(False)
+ self.policy_registry_filepath = policy_registry_filepath or UNINITIALIZED_CONFIGURATION
+
# Blockchain
self.poa = poa
self.is_light = light
@@ -495,6 +500,7 @@ class CharacterConfiguration(BaseConfiguration):
self.is_light = False
self.eth_provider_uri = None
self.registry_filepath = None
+ self.policy_registry_filepath = None
self.gas_strategy = None
self.max_gas_price = None
@@ -533,13 +539,27 @@ class CharacterConfiguration(BaseConfiguration):
self.testnet = self.domain != NetworksInventory.MAINNET
self.signer = Signer.from_signer_uri(self.signer_uri, testnet=self.testnet)
- # Onchain Payments
- # TODO: Enforce this for Ursula/Alice but not Bob?
- # if not payment_provider:
- # raise self.ConfigurationError("payment provider is required.")
- self.payment_method = payment_method or self.DEFAULT_PAYMENT_METHOD
- self.payment_network = payment_network or self.DEFAULT_PAYMENT_NETWORK
- self.payment_provider = payment_provider or (self.eth_provider_uri or None) # default to L1 payments
+ #
+ # Onchain Payments & Policies
+ #
+
+ # FIXME: Enforce this for Ursula/Alice but not Bob?
+ from nucypher.config.characters import BobConfiguration
+ if not isinstance(self, BobConfiguration):
+ # if not payment_provider:
+ # raise self.ConfigurationError("payment provider is required.")
+ self.payment_method = payment_method or self.DEFAULT_PAYMENT_METHOD
+ self.payment_network = payment_network or self.DEFAULT_PAYMENT_NETWORK
+ self.payment_provider = payment_provider or (self.eth_provider_uri or None) # default to L1 payments
+
+ # TODO: Dedupe
+ if not self.policy_registry:
+ if not self.policy_registry_filepath:
+ self.log.info(f"Fetching latest policy registry from source.")
+ self.policy_registry = InMemoryContractRegistry.from_latest_publication(network=self.payment_network)
+ else:
+ self.policy_registry = LocalContractRegistry(filepath=self.policy_registry_filepath)
+ self.log.info(f"Using local policy registry ({self.policy_registry}).")
if dev_mode:
self.__temp_dir = UNINITIALIZED_CONFIGURATION
@@ -618,12 +638,21 @@ class CharacterConfiguration(BaseConfiguration):
return self.__dev_mode
def _setup_node_storage(self, node_storage=None) -> None:
- if self.dev_mode:
- node_storage = ForgetfulNodeStorage(registry=self.registry, federated_only=self.federated_only)
- elif not node_storage:
- node_storage = LocalFileBasedNodeStorage(registry=self.registry,
- config_root=self.config_root,
- federated_only=self.federated_only)
+ # TODO: Disables node metadata persistence..
+ # if self.dev_mode:
+ # node_storage = ForgetfulNodeStorage(registry=self.registry, federated_only=self.federated_only)
+
+ # TODO: Forcibly clears the filesystem of any stored node metadata and certificates...
+ local_node_storage = LocalFileBasedNodeStorage(
+ registry=self.registry,
+ config_root=self.config_root,
+ federated_only=self.federated_only
+ )
+ local_node_storage.clear()
+ self.log.info(f'Cleared peer metadata from {local_node_storage.root_dir}')
+
+ # TODO: Always sets up nodes for in-memory node metadata storage
+ node_storage = ForgetfulNodeStorage(registry=self.registry, federated_only=self.federated_only)
self.node_storage = node_storage
def forget_nodes(self) -> None:
@@ -869,7 +898,8 @@ class CharacterConfiguration(BaseConfiguration):
if payment_class.ONCHAIN:
# on-chain payment strategies require a blockchain connection
payment_strategy = payment_class(network=self.payment_network,
- provider=self.payment_provider)
+ eth_provider=self.payment_provider,
+ registry=self.policy_registry)
else:
payment_strategy = payment_class()
return payment_strategy
diff --git a/nucypher/config/constants.py b/nucypher/config/constants.py
index 05a066c79..4dbe0f16c 100644
--- a/nucypher/config/constants.py
+++ b/nucypher/config/constants.py
@@ -29,7 +29,7 @@ import nucypher
# Environment variables
NUCYPHER_ENVVAR_KEYSTORE_PASSWORD = "NUCYPHER_KEYSTORE_PASSWORD"
NUCYPHER_ENVVAR_OPERATOR_ADDRESS = "NUCYPHER_OPERATOR_ADDRESS"
-NUCYPHER_ENVVAR_OPERATOR_ETH_PASSWORD = "NUCYPHER_WORKER_ETH_PASSWORD"
+NUCYPHER_ENVVAR_OPERATOR_ETH_PASSWORD = "NUCYPHER_OPERATOR_ETH_PASSWORD"
NUCYPHER_ENVVAR_STAKING_PROVIDER_ETH_PASSWORD = "NUCYPHER_STAKING_PROVIDER_ETH_PASSWORD"
NUCYPHER_ENVVAR_ALICE_ETH_PASSWORD = "NUCYPHER_ALICE_ETH_PASSWORD"
NUCYPHER_ENVVAR_BOB_ETH_PASSWORD = "NUCYPHER_BOB_ETH_PASSWORD"
@@ -75,4 +75,4 @@ TEMPORARY_DOMAIN = ":temporary-domain:" # for use with `--dev` node runtimes
NUCYPHER_EVENTS_THROTTLE_MAX_BLOCKS = 'NUCYPHER_EVENTS_THROTTLE_MAX_BLOCKS'
# Probationary period
-END_OF_POLICIES_PROBATIONARY_PERIOD = MayaDT.from_iso8601('2022-3-17T23:59:59.0Z')
+END_OF_POLICIES_PROBATIONARY_PERIOD = MayaDT.from_iso8601('2022-6-16T23:59:59.0Z')
diff --git a/nucypher/config/storages.py b/nucypher/config/storages.py
index e477b2a92..9a057a1fc 100644
--- a/nucypher/config/storages.py
+++ b/nucypher/config/storages.py
@@ -49,7 +49,7 @@ class NodeStorage(ABC):
pass
def __init__(self,
- federated_only: bool, # TODO# 466
+ federated_only: bool = False, # TODO# 466
character_class=None,
registry: BaseContractRegistry = None,
) -> None:
diff --git a/nucypher/network/exceptions.py b/nucypher/network/exceptions.py
index e6e861ada..eb832881f 100644
--- a/nucypher/network/exceptions.py
+++ b/nucypher/network/exceptions.py
@@ -18,8 +18,10 @@
import requests
import socket
-NodeSeemsToBeDown = (requests.exceptions.ConnectionError,
- requests.exceptions.ReadTimeout,
- requests.exceptions.ConnectTimeout,
- socket.gaierror,
- ConnectionRefusedError)
+NodeSeemsToBeDown = (
+ requests.exceptions.ConnectionError,
+ requests.exceptions.ReadTimeout,
+ requests.exceptions.ConnectTimeout,
+ socket.gaierror,
+ ConnectionRefusedError
+)
diff --git a/nucypher/network/middleware.py b/nucypher/network/middleware.py
index 9aa06ff8d..6a2792290 100644
--- a/nucypher/network/middleware.py
+++ b/nucypher/network/middleware.py
@@ -16,21 +16,27 @@ along with nucypher. If not, see .
"""
-from http import HTTPStatus
import socket
import ssl
import time
+from http import HTTPStatus
+from pathlib import Path
+from typing import Optional, Tuple
from typing import Sequence
+
import requests
-
-from nucypher_core import MetadataRequest, FleetStateChecksum, NodeMetadata
-
-from constant_sorrow.constants import CERTIFICATE_NOT_SAVED, EXEMPT_FROM_VERIFICATION
+from constant_sorrow.constants import EXEMPT_FROM_VERIFICATION
from cryptography import x509
from cryptography.hazmat.backends import default_backend
+from cryptography.x509 import Certificate
+from nucypher_core import MetadataRequest, FleetStateChecksum, NodeMetadata
+from nucypher.blockchain.eth.registry import BaseContractRegistry
+from nucypher.config.storages import ForgetfulNodeStorage
+from nucypher.network.exceptions import NodeSeemsToBeDown
from nucypher.utilities.logging import Logger
+SSL_LOGGER = Logger('ssl-middleware')
EXEMPT_FROM_VERIFICATION.bool_value(False)
@@ -38,9 +44,46 @@ class NucypherMiddlewareClient:
library = requests
timeout = 1.2
- def __init__(self, registry=None, eth_provider_uri: str = None, *args, **kwargs):
+ def __init__(self,
+ registry: Optional['BaseContractRegistry'] = None,
+ eth_provider_uri: Optional[str] = None,
+ storage: Optional['NodeStorage'] = None,
+ *args, **kwargs):
+
self.registry = registry
self.eth_provider_uri = eth_provider_uri
+ self.storage = storage or ForgetfulNodeStorage() # for certificate storage
+
+ def get_certificate(self,
+ host,
+ port,
+ timeout=3,
+ retry_attempts: int = 3,
+ retry_rate: int = 2,
+ current_attempt: int = 0):
+
+ socket.setdefaulttimeout(timeout) # Set Socket Timeout
+
+ try:
+ SSL_LOGGER.info(f"Fetching {host}:{port} TLS certificate")
+ certificate_pem = ssl.get_server_certificate(addr=(host, port))
+ certificate = ssl.PEM_cert_to_DER_cert(certificate_pem)
+
+ except socket.timeout:
+ if current_attempt == retry_attempts:
+ message = f"No Response from {host}:{port} after {retry_attempts} attempts"
+ SSL_LOGGER.info(message)
+ raise ConnectionRefusedError("No response from {}:{}".format(host, port))
+ SSL_LOGGER.info(f"No Response from {host}:{port}. Retrying in {retry_rate} seconds...")
+ time.sleep(retry_rate)
+ return self.get_certificate(host, port, timeout, retry_attempts, retry_rate, current_attempt + 1)
+
+ except OSError:
+ raise # TODO: #1835
+
+ certificate = x509.load_der_x509_certificate(certificate, backend=default_backend())
+ filepath = self.storage.store_node_certificate(certificate=certificate, port=port)
+ return certificate, filepath
@staticmethod
def response_cleaner(response):
@@ -63,15 +106,10 @@ class NucypherMiddlewareClient:
if node:
if any((host, port)):
raise ValueError("Don't pass host and port if you are passing the node.")
- host = node.rest_url()
- certificate_filepath = node.certificate_filepath
- elif all((host, port)):
- host = f"{host}:{port}"
- certificate_filepath = CERTIFICATE_NOT_SAVED
- else:
+ host, port = node.rest_interface.host, node.rest_interface.port
+ elif not (host and port):
raise ValueError("You need to pass either the node or a host and port.")
-
- return host, certificate_filepath, self.library
+ return host, port, self.library
def invoke_method(self, method, url, *args, **kwargs):
self.clean_params(kwargs)
@@ -86,13 +124,12 @@ class NucypherMiddlewareClient:
No cleaning needed.
"""
- def node_information(self, host, port, certificate_filepath=None):
+ def node_information(self, host, port):
# The only time a node is exempt from verification - when we are first getting its info.
response = self.get(node_or_sprout=EXEMPT_FROM_VERIFICATION,
host=host, port=port,
path="public_information",
- timeout=2,
- certificate_filepath=certificate_filepath)
+ timeout=2)
return response.content
def __getattr__(self, method_name):
@@ -104,38 +141,44 @@ class NucypherMiddlewareClient:
node_or_sprout=None,
host=None,
port=None,
- certificate_filepath=None,
*args, **kwargs):
- host, node_certificate_filepath, http_client = self.verify_and_parse_node_or_host_and_port(node_or_sprout, host, port)
-
- if certificate_filepath:
- filepaths_are_different = node_certificate_filepath != certificate_filepath
- node_has_a_cert = node_certificate_filepath is not CERTIFICATE_NOT_SAVED
- if node_has_a_cert and filepaths_are_different:
- raise ValueError("Don't try to pass a node with a certificate_filepath while also passing a"
- " different certificate_filepath. What do you even expect?")
- else:
- certificate_filepath = node_certificate_filepath
+ # Get interface
+ host, port, http_client = self.verify_and_parse_node_or_host_and_port(node_or_sprout, host, port)
+ endpoint = f"https://{host}:{port}/{path}"
method = getattr(http_client, method_name)
- url = f"https://{host}/{path}"
- response = self.invoke_method(method, url, verify=certificate_filepath, *args, **kwargs)
+ # Fetch SSL certificate
+ try:
+ certificate, filepath = self.get_certificate(host=host, port=port)
+ except NodeSeemsToBeDown as e:
+ raise RestMiddleware.Unreachable(message=f'Node {node_or_sprout} {host}:{port} is unreachable: {e}')
+
+ # Send request
+ response = self.invoke_method(method, endpoint, verify=filepath, *args, **kwargs)
+
+ # Handle response
cleaned_response = self.response_cleaner(response)
if cleaned_response.status_code >= 300:
+
if cleaned_response.status_code == HTTPStatus.BAD_REQUEST:
raise RestMiddleware.BadRequest(reason=cleaned_response.json)
+
elif cleaned_response.status_code == HTTPStatus.NOT_FOUND:
m = f"While trying to {method_name} {args} ({kwargs}), server 404'd. Response: {cleaned_response.content}"
raise RestMiddleware.NotFound(m)
+
elif cleaned_response.status_code == HTTPStatus.PAYMENT_REQUIRED:
# TODO: Use this as a hook to prompt Bob's payment for policy sponsorship
# https://getyarn.io/yarn-clip/ce0d37ba-4984-4210-9a40-c9c9859a3164
raise RestMiddleware.PaymentRequired(cleaned_response.content)
+
elif cleaned_response.status_code == HTTPStatus.FORBIDDEN:
raise RestMiddleware.Unauthorized(cleaned_response.content)
+
else:
raise RestMiddleware.UnexpectedResponse(cleaned_response.content, status=cleaned_response.status_code)
+
return cleaned_response
return method_wrapper
@@ -152,6 +195,10 @@ class RestMiddleware:
_client_class = NucypherMiddlewareClient
+ class Unreachable(Exception):
+ def __init__(self, message, *args, **kwargs):
+ super().__init__(message, *args, **kwargs)
+
class UnexpectedResponse(Exception):
def __init__(self, message, status, *args, **kwargs):
super().__init__(message, *args, **kwargs)
@@ -179,33 +226,6 @@ class RestMiddleware:
def __init__(self, registry=None, eth_provider_uri: str = None):
self.client = self._client_class(registry=registry, eth_provider_uri=eth_provider_uri)
- def get_certificate(self, host, port, timeout=3, retry_attempts: int = 3, retry_rate: int = 2,
- current_attempt: int = 0):
-
- socket.setdefaulttimeout(timeout) # Set Socket Timeout
-
- try:
- self.log.info(f"Fetching seednode {host}:{port} TLS certificate")
- seednode_certificate_pem = ssl.get_server_certificate(addr=(host, port))
- seednode_certificate = ssl.PEM_cert_to_DER_cert(seednode_certificate_pem)
-
- except socket.timeout:
- if current_attempt == retry_attempts:
- message = f"No Response from seednode {host}:{port} after {retry_attempts} attempts"
- self.log.info(message)
- raise ConnectionRefusedError("No response from {}:{}".format(host, port))
- self.log.info(f"No Response from seednode {host}:{port}. Retrying in {retry_rate} seconds...")
- time.sleep(retry_rate)
- return self.get_certificate(host, port, timeout, retry_attempts, retry_rate, current_attempt + 1)
-
- except OSError:
- raise # TODO: #1835
-
- else:
- certificate = x509.load_der_x509_certificate(seednode_certificate,
- backend=default_backend())
- return certificate
-
def request_revocation(self, ursula, revocation):
# TODO: Implement offchain revocation #2787
response = self.client.post(
diff --git a/nucypher/network/nodes.py b/nucypher/network/nodes.py
index 8fc1d37c1..72a1992fe 100644
--- a/nucypher/network/nodes.py
+++ b/nucypher/network/nodes.py
@@ -24,7 +24,6 @@ from typing import Callable, List, Optional, Set, Tuple, Union
import maya
import requests
-from constant_sorrow import constant_or_bytes
from constant_sorrow.constants import (
CERTIFICATE_NOT_SAVED,
FLEET_STATES_MATCH,
@@ -32,19 +31,18 @@ from constant_sorrow.constants import (
NO_STORAGE_AVAILABLE,
RELAX,
)
-from cryptography.x509 import Certificate, load_der_x509_certificate
from cryptography.hazmat.backends import default_backend
+from cryptography.x509 import Certificate
+from cryptography.x509 import load_der_x509_certificate
from eth_utils import to_checksum_address
+from nucypher_core import NodeMetadata, MetadataResponse, MetadataResponsePayload
+from nucypher_core.umbral import Signature
from requests.exceptions import SSLError
from twisted.internet import reactor, task
from twisted.internet.defer import Deferred
-from nucypher_core import NodeMetadata, MetadataResponse, MetadataResponsePayload
-from nucypher_core.umbral import Signature
-
from nucypher.acumen.nicknames import Nickname
from nucypher.acumen.perception import FleetSensor
-from nucypher.blockchain.economics import EconomicsFactory
from nucypher.blockchain.eth.agents import ContractAgency, PREApplicationAgent
from nucypher.blockchain.eth.constants import NULL_ADDRESS
from nucypher.blockchain.eth.networks import NetworksInventory
@@ -58,7 +56,6 @@ from nucypher.crypto.powers import (
SigningPower,
)
from nucypher.crypto.signing import SignatureStamp, InvalidSignature
-from nucypher.crypto.utils import recover_address_eip_191, verify_eip_191
from nucypher.network.exceptions import NodeSeemsToBeDown
from nucypher.network.middleware import RestMiddleware
from nucypher.network.protocols import SuspiciousActivity, InterfaceInfo
@@ -475,7 +472,7 @@ class Learner:
# TODO: Bucket this node as having bad TLS info - maybe it's an update that hasn't fully propagated? 567
return False
- except NodeSeemsToBeDown:
+ except RestMiddleware.Unreachable:
self.log.info("No Response while trying to verify node {}|{}".format(node.rest_interface, node))
# TODO: Bucket this node as "ghost" or something: somebody else knows about it, but we can't get to it. 567
return False
@@ -805,15 +802,14 @@ class Learner:
# These except clauses apply to the current_teacher itself, not the learned-about nodes.
except NodeSeemsToBeDown as e:
unresponsive_nodes.add(current_teacher)
- self.log.info(
- f"Teacher {str(current_teacher)} is perhaps down:{e}.") # FIXME: This was printing the node bytestring. Is this really necessary? #1712
+ 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 (hex={bytes(current_teacher.metadata()).hex()}):{e}.")
+ self.log.warn(f"Teacher {str(current_teacher)} is invalid: {e}.")
# TODO (#567): bucket the node as suspicious
return
except RuntimeError as e:
@@ -918,8 +914,8 @@ class Learner:
# except sprout.Invalidsprout:
# self.log.warn(sprout.invalid_metadata_message.format(sprout))
- except InvalidNodeCertificate as e:
- message = f"Discovered sprout with invalid node certificate: {sprout}. Full error: {e.__str__()} "
+ except NodeSeemsToBeDown as e:
+ message = f"Node is unreachable: {sprout}. Full error: {e.__str__()} "
self.log.warn(message)
except SuspiciousActivity:
@@ -973,7 +969,7 @@ class Teacher:
# Assume unverified
self.verified_stamp = False
- self.verified_worker = False
+ self.verified_operator = False
self.verified_metadata = False
self.verified_node = False
@@ -1054,7 +1050,7 @@ class Teacher:
is_staking = application_agent.is_authorized(staking_provider=self.checksum_address) # checksum address here is staking provider
return is_staking
- def validate_worker(self, registry: BaseContractRegistry = None, eth_provider_uri: Optional[str] = None) -> None:
+ def validate_operator(self, registry: BaseContractRegistry = None, eth_provider_uri: Optional[str] = None) -> None:
# Federated
if self.federated_only:
@@ -1068,23 +1064,28 @@ class Teacher:
# Try to derive the worker address if it hasn't been derived yet.
try:
- operator_address = self.operator_address
+ # TODO: This is overtly implicit
+ _operator_address = self.operator_address
except Exception as e:
raise self.InvalidOperatorSignature(str(e)) from e
+ self.verified_stamp = True # TODO: Does this belong here?
# On-chain staking check, if registry is present
if registry:
+
if not self._operator_is_bonded(registry=registry): # <-- Blockchain CALL
message = f"Operator {self.operator_address} is not bonded to staking provider {self.checksum_address}"
self.log.debug(message)
raise self.UnbondedOperator(message)
if self._staking_provider_is_really_staking(registry=registry, eth_provider_uri=eth_provider_uri): # <-- Blockchain CALL
- self.verified_worker = True
+ self.log.info(f'Verified operator {self}')
+ self.verified_operator = True
else:
raise self.NotStaking(f"{self.checksum_address} is not staking")
- self.verified_stamp = True
+ else:
+ self.log.info('No registry provided for staking verification.')
def validate_metadata_signature(self) -> bool:
"""Checks that the interface info is valid for this node's canonical address."""
@@ -1107,7 +1108,7 @@ class Teacher:
# Offline check of valid stamp signature by worker
try:
- self.validate_worker(registry=registry, eth_provider_uri=eth_provider_uri)
+ self.validate_operator(registry=registry, eth_provider_uri=eth_provider_uri)
except self.WrongMode:
if bool(registry):
raise
@@ -1137,7 +1138,7 @@ class Teacher:
self.verified_metadata = False
self.verified_node = False
self.verified_stamp = False
- self.verified_worker = False
+ self.verified_operator = False
if self.verified_node:
return True
@@ -1156,8 +1157,7 @@ class Teacher:
certificate_filepath = self.certificate_filepath
response_data = network_middleware_client.node_information(host=self.rest_interface.host,
- port=self.rest_interface.port,
- certificate_filepath=certificate_filepath)
+ port=self.rest_interface.port)
try:
sprout = self.from_metadata_bytes(response_data)
diff --git a/nucypher/network/server.py b/nucypher/network/server.py
index 3fd794b9b..f8b4eb192 100644
--- a/nucypher/network/server.py
+++ b/nucypher/network/server.py
@@ -16,9 +16,9 @@ along with nucypher. If not, see .
"""
-from http import HTTPStatus
import uuid
import weakref
+from http import HTTPStatus
from pathlib import Path
from typing import Tuple
@@ -27,14 +27,13 @@ from constant_sorrow.constants import RELAX
from flask import Flask, Response, jsonify, request
from mako import exceptions as mako_exceptions
from mako.template import Template
-
from nucypher_core import (
ReencryptionRequest,
RevocationOrder,
MetadataRequest,
MetadataResponse,
MetadataResponsePayload,
- )
+)
from nucypher.config.constants import MAX_UPLOAD_CONTENT_LENGTH
from nucypher.crypto.keypairs import DecryptingKeypair
@@ -42,8 +41,8 @@ from nucypher.crypto.signing import InvalidSignature
from nucypher.datastore.datastore import Datastore
from nucypher.datastore.models import ReencryptionRequest as ReencryptionRequestModel
from nucypher.network.exceptions import NodeSeemsToBeDown
-from nucypher.network.protocols import InterfaceInfo
from nucypher.network.nodes import NodeSprout
+from nucypher.network.protocols import InterfaceInfo
from nucypher.utilities.logging import Logger
HERE = BASE_DIR = Path(__file__).parent
@@ -105,7 +104,6 @@ def _make_rest_app(datastore: Datastore, this_node, log: Logger) -> Flask:
# TODO: Avoid circular imports :-(
from nucypher.characters.lawful import Alice, Bob, Ursula
- from nucypher.policy.policies import Policy
_alice_class = Alice
_bob_class = Bob
@@ -209,7 +207,7 @@ def _make_rest_app(datastore: Datastore, this_node, log: Logger) -> Flask:
# TODO: Evaluate multiple reencryption prerequisites & enforce policy expiration
paid = this_node.payment_method.verify(payee=this_node.checksum_address, request=reenc_request)
if not paid:
- message = f"{bob_identity_message} Policy {hrac} is unpaid."
+ message = f"{bob_identity_message} Policy {bytes(hrac)} is unpaid."
return Response(message, status=HTTPStatus.PAYMENT_REQUIRED)
# Re-encrypt
@@ -255,13 +253,9 @@ def _make_rest_app(datastore: Datastore, this_node, log: Logger) -> Flask:
# Make a Sandwich
try:
- # Fetch and store initiator's teacher certificate.
- certificate = this_node.network_middleware.get_certificate(host=initiator_address, port=initiator_port)
- certificate_filepath = this_node.node_storage.store_node_certificate(certificate=certificate)
requesting_ursula_metadata = this_node.network_middleware.client.node_information(
host=initiator_address,
port=initiator_port,
- certificate_filepath=certificate_filepath
)
except NodeSeemsToBeDown:
return Response({'error': 'Unreachable node'}, status=HTTPStatus.BAD_REQUEST) # ... toasted
diff --git a/nucypher/policy/payment.py b/nucypher/policy/payment.py
index 7e489f8d4..320cc20a0 100644
--- a/nucypher/policy/payment.py
+++ b/nucypher/policy/payment.py
@@ -15,15 +15,16 @@
along with nucypher. If not, see .
"""
+
from abc import ABC, abstractmethod
from typing import Optional, NamedTuple, Dict
import maya
from nucypher_core import ReencryptionRequest
-from web3.types import Wei, ChecksumAddress, Timestamp, TxReceipt
+from web3.types import Wei, Timestamp, TxReceipt, ChecksumAddress
from nucypher.blockchain.eth.agents import SubscriptionManagerAgent
-from nucypher.blockchain.eth.registry import InMemoryContractRegistry
+from nucypher.blockchain.eth.registry import InMemoryContractRegistry, BaseContractRegistry
from nucypher.policy.policies import BlockchainPolicy, Policy
@@ -91,11 +92,17 @@ class ContractPayment(PaymentMethod, ABC):
rate: Wei
value: Wei
- def __init__(self, provider: str, network: str, *args, **kwargs):
+ def __init__(self,
+ eth_provider: str,
+ network: str,
+ registry: Optional[BaseContractRegistry] = None,
+ *args, **kwargs):
super().__init__(*args, **kwargs)
- self.provider = provider
+ self.provider = eth_provider
self.network = network
- self.registry = InMemoryContractRegistry.from_latest_publication(network=network)
+ if not registry:
+ registry = InMemoryContractRegistry.from_latest_publication(network=network)
+ self.registry = registry
self.__agent = None # delay blockchain/registry reads until later
@property
diff --git a/nucypher/utilities/networking.py b/nucypher/utilities/networking.py
index 0352bab77..a6ec3385a 100644
--- a/nucypher/utilities/networking.py
+++ b/nucypher/utilities/networking.py
@@ -17,11 +17,12 @@
import random
-import requests
from ipaddress import ip_address
-from requests.exceptions import RequestException, HTTPError
from typing import Union, Optional
+import requests
+from requests.exceptions import RequestException, HTTPError
+
from nucypher.acumen.perception import FleetSensor
from nucypher.blockchain.eth.registry import BaseContractRegistry
from nucypher.config.storages import LocalFileBasedNodeStorage
diff --git a/tests/acceptance/blockchain/agents/test_contract_agency.py b/tests/acceptance/blockchain/agents/test_contract_agency.py
index b4c37d890..8bab62af2 100644
--- a/tests/acceptance/blockchain/agents/test_contract_agency.py
+++ b/tests/acceptance/blockchain/agents/test_contract_agency.py
@@ -22,10 +22,10 @@ def test_get_agent_with_different_registries(application_economics, agency, test
# Get agents using same registry instance
application_agent_1 = ContractAgency.get_agent(PREApplicationAgent, registry=test_registry)
application_agent_2 = ContractAgency.get_agent(PREApplicationAgent, registry=test_registry)
- assert application_agent_2.registry_str == application_agent_1.registry_str == str(test_registry)
+ assert application_agent_2.registry == application_agent_1.registry == test_registry
assert application_agent_2 is application_agent_1
# Same content but different classes of registries
application_agent_2 = ContractAgency.get_agent(PREApplicationAgent, registry=agency_local_registry)
- assert application_agent_2.registry_str == str(test_registry)
+ assert application_agent_2.registry == test_registry
assert application_agent_2 is application_agent_1
diff --git a/tests/acceptance/characters/test_decentralized_grant.py b/tests/acceptance/characters/test_decentralized_grant.py
index 88b42afe6..d497e41c1 100644
--- a/tests/acceptance/characters/test_decentralized_grant.py
+++ b/tests/acceptance/characters/test_decentralized_grant.py
@@ -47,7 +47,7 @@ def check(policy, bob, ursulas):
def test_decentralized_grant_subscription_manager(blockchain_alice, blockchain_bob, blockchain_ursulas):
- payment_method = SubscriptionManagerPayment(provider=TEST_ETH_PROVIDER_URI, network=TEMPORARY_DOMAIN)
+ payment_method = SubscriptionManagerPayment(eth_provider=TEST_ETH_PROVIDER_URI, network=TEMPORARY_DOMAIN)
blockchain_alice.payment_method = payment_method
policy = blockchain_alice.grant(bob=blockchain_bob,
label=os.urandom(16),
diff --git a/tests/acceptance/characters/test_ursula_prepares_to_act_as_worker.py b/tests/acceptance/characters/test_ursula_prepares_to_act_as_worker.py
index 2b4466995..812efd247 100644
--- a/tests/acceptance/characters/test_ursula_prepares_to_act_as_worker.py
+++ b/tests/acceptance/characters/test_ursula_prepares_to_act_as_worker.py
@@ -36,8 +36,8 @@ def test_stakers_bond_to_ursulas(testerchain, test_registry, staking_providers,
assert len(ursulas) == len(staking_providers)
for ursula in ursulas:
- ursula.validate_worker(registry=test_registry)
- assert ursula.verified_worker
+ ursula.validate_operator(registry=test_registry)
+ assert ursula.verified_operator
def test_blockchain_ursula_substantiates_stamp(blockchain_ursulas):
@@ -55,7 +55,7 @@ def test_blockchain_ursula_verifies_stamp(blockchain_ursulas):
# This Ursula does not yet have a verified stamp
first_ursula.verified_stamp = False
- first_ursula.validate_worker()
+ first_ursula.validate_operator()
# ...but now it's verified.
assert first_ursula.verified_stamp
@@ -138,7 +138,7 @@ def test_vladimir_invalidity_without_stake(testerchain, blockchain_ursulas, bloc
# But the actual handshake proves him wrong.
message = "Wallet address swapped out. It appears that someone is trying to defraud this node."
with pytest.raises(vladimir.InvalidNode, match=message):
- vladimir.verify_node(blockchain_alice.network_middleware.client, certificate_filepath="doesn't matter")
+ vladimir.verify_node(blockchain_alice.network_middleware.client)
# TODO: Change name of this file, extract this test
diff --git a/tests/acceptance/cli/test_alice.py b/tests/acceptance/cli/test_alice.py
index 03cadf609..cd9a0a660 100644
--- a/tests/acceptance/cli/test_alice.py
+++ b/tests/acceptance/cli/test_alice.py
@@ -115,7 +115,10 @@ def test_initialize_alice_with_custom_configuration_root(custom_filepath, click_
# Files and Directories
assert custom_filepath.is_dir(), 'Configuration file does not exist'
assert (custom_filepath / 'keystore').is_dir(), 'Keystore does not exist'
- assert (custom_filepath / 'known_nodes').is_dir(), 'known_nodes directory 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 / AliceConfiguration.generate_filename()
assert custom_config_filepath.is_file(), 'Configuration file does not exist'
diff --git a/tests/acceptance/cli/test_bob.py b/tests/acceptance/cli/test_bob.py
index 56b1af71e..63b1ef572 100644
--- a/tests/acceptance/cli/test_bob.py
+++ b/tests/acceptance/cli/test_bob.py
@@ -64,7 +64,10 @@ def test_initialize_bob_with_custom_configuration_root(click_runner, custom_file
# Files and Directories
assert custom_filepath.is_dir(), 'Configuration file does not exist'
assert (custom_filepath / 'keystore').is_dir(), 'Keystore does not exist'
- assert (custom_filepath / 'known_nodes').is_dir(), 'known_nodes directory 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 / BobConfiguration.generate_filename()
assert custom_config_filepath.is_file(), 'Configuration file does not exist'
diff --git a/tests/acceptance/cli/test_cli_config.py b/tests/acceptance/cli/test_cli_config.py
index e7456f495..2b8221c98 100644
--- a/tests/acceptance/cli/test_cli_config.py
+++ b/tests/acceptance/cli/test_cli_config.py
@@ -67,8 +67,10 @@ def test_initialize_via_cli(config_class, custom_filepath: Path, click_runner, m
# Files and Directories
assert custom_filepath.is_dir(), 'Configuration file does not exist'
assert (custom_filepath / 'keystore').is_dir(), 'Keystore does not exist'
- assert (custom_filepath / 'known_nodes').is_dir(), 'known_nodes directory 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(click_runner, custom_filepath: Path, config_class, monkeypatch, test_registry, test_registry_source_manager):
diff --git a/tests/acceptance/cli/test_mixed_configurations.py b/tests/acceptance/cli/test_mixed_configurations.py
index 097972bd2..a5a871006 100644
--- a/tests/acceptance/cli/test_mixed_configurations.py
+++ b/tests/acceptance/cli/test_mixed_configurations.py
@@ -284,7 +284,14 @@ def test_corrupted_configuration(click_runner,
# Ensure configuration creation
top_level_config_root = [f.name for f in custom_filepath.iterdir()]
assert default_filename in top_level_config_root, "JSON configuration file was not created"
- for field in ['known_nodes', 'keystore', default_filename]:
+
+ expected_fields = [
+ # TODO: Only using in-memory node storage for now
+ # 'known_nodes',
+ 'keystore',
+ default_filename
+ ]
+ for field in expected_fields:
assert field in top_level_config_root
# "Corrupt" the configuration by removing the contract registry
diff --git a/tests/acceptance/cli/ursula/test_federated_ursula.py b/tests/acceptance/cli/ursula/test_federated_ursula.py
index 3d4fd05a0..455c32924 100644
--- a/tests/acceptance/cli/ursula/test_federated_ursula.py
+++ b/tests/acceptance/cli/ursula/test_federated_ursula.py
@@ -84,7 +84,10 @@ def test_initialize_custom_configuration_root(click_runner, custom_filepath: Pat
# Files and Directories
assert custom_filepath.is_dir(), 'Configuration file does not exist'
assert (custom_filepath / 'keystore').is_dir(), 'KEYSTORE does not exist'
- assert (custom_filepath / 'known_nodes').is_dir(), 'known_nodes directory 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'
diff --git a/tests/acceptance/learning/test_fault_tolerance.py b/tests/acceptance/learning/test_fault_tolerance.py
index 90301cc54..e04276902 100644
--- a/tests/acceptance/learning/test_fault_tolerance.py
+++ b/tests/acceptance/learning/test_fault_tolerance.py
@@ -134,7 +134,7 @@ def test_invalid_operators_tolerance(testerchain,
testerchain.time_travel(periods=1)
# The worker is valid and can be verified (even with the force option)
- worker.verify_node(force=True, network_middleware=MockRestMiddleware(), certificate_filepath="quietorl")
+ worker.verify_node(force=True, network_middleware=MockRestMiddleware())
# In particular, we know that it's bonded to a staker who is really staking.
assert worker._operator_is_bonded(registry=test_registry)
assert worker._staking_provider_is_really_staking(registry=test_registry)
@@ -159,11 +159,11 @@ def test_invalid_operators_tolerance(testerchain,
assert 0 == staking_agent.owned_tokens(idle_staker.checksum_address)
# ... but the worker node still is "verified" (since we're not forcing on-chain verification)
- worker.verify_node(network_middleware=MockRestMiddleware(), certificate_filepath="quietorl")
+ worker.verify_node(network_middleware=MockRestMiddleware())
# If we force, on-chain verification, the worker is of course not verified
with pytest.raises(worker.NotStaking):
- worker.verify_node(force=True, network_middleware=MockRestMiddleware(), certificate_filepath="quietorl")
+ worker.verify_node(force=True, network_middleware=MockRestMiddleware())
# Let's learn from this invalid node
lonely_blockchain_learner._current_teacher_node = worker
diff --git a/tests/integration/cli/actions/test_auth_actions.py b/tests/integration/cli/actions/test_auth_actions.py
index 64bbb124f..f070e955b 100644
--- a/tests/integration/cli/actions/test_auth_actions.py
+++ b/tests/integration/cli/actions/test_auth_actions.py
@@ -104,7 +104,12 @@ def test_get_nucypher_password(mock_stdin, mock_account, confirm, capsys):
assert prompt in captured.out
-def test_unlock_nucypher_keystore_invalid_password(mocker, test_emitter, alice_blockchain_test_config, capsys, tmpdir):
+def test_unlock_nucypher_keystore_invalid_password(mocker,
+ test_emitter,
+ alice_blockchain_test_config,
+ capsys,
+ tmpdir,
+ test_registry_source_manager):
# Setup
mocker.patch.object(passwords, 'secret_box_decrypt', side_effect=SecretBoxAuthenticationError)
diff --git a/tests/integration/config/test_character_configuration.py b/tests/integration/config/test_character_configuration.py
index efb35f416..d7ebe1776 100644
--- a/tests/integration/config/test_character_configuration.py
+++ b/tests/integration/config/test_character_configuration.py
@@ -95,9 +95,12 @@ 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, tmpdir):
+def test_default_character_configuration_preservation(configuration_class,
+ mock_testerchain,
+ test_registry_source_manager,
+ tmpdir,
+ test_registry):
configuration_class.DEFAULT_CONFIG_ROOT = Path('/tmp')
fake_address = '0xdeadbeef'
@@ -121,10 +124,15 @@ def test_default_character_configuration_preservation(configuration_class, teste
domain=network,
rest_host=MOCK_IP_ADDRESS,
payment_provider=MOCK_ETH_PROVIDER_URI,
+ policy_registry=test_registry,
+ payment_network=TEMPORARY_DOMAIN,
keystore=keystore)
else:
- character_config = configuration_class(checksum_address=fake_address, domain=network)
+ character_config = configuration_class(checksum_address=fake_address,
+ domain=network,
+ payment_network=TEMPORARY_DOMAIN,
+ policy_registry=test_registry)
generated_filepath = character_config.generate_filepath()
assert generated_filepath == expected_filepath
diff --git a/tests/unit/characters/control/test_character_fields.py b/tests/unit/characters/control/test_character_fields.py
index d98e786b0..b0588007d 100644
--- a/tests/unit/characters/control/test_character_fields.py
+++ b/tests/unit/characters/control/test_character_fields.py
@@ -15,15 +15,14 @@ You should have received a copy of the GNU Affero General Public License
along with nucypher. If not, see .
"""
import datetime
-from base64 import b64encode, b64decode
+from base64 import b64encode
import maya
import pytest
-
from nucypher_core import (
MessageKit as MessageKitClass,
EncryptedTreasureMap as EncryptedTreasureMapClass)
-from nucypher_core.umbral import SecretKey, Signer
+from nucypher_core.umbral import SecretKey
from nucypher.characters.control.specifications.fields import (
DateTime,
@@ -48,6 +47,7 @@ from nucypher.control.specifications.exceptions import InvalidInputData
# assert deserialized == data
+
def test_file(tmpdir):
text = b"I never saw a wild thing sorry for itself. A small bird will drop frozen dead from a bough without " \
b"ever having felt sorry for itself." # -- D.H. Lawrence
diff --git a/tests/unit/test_external_ip_utilities.py b/tests/unit/test_external_ip_utilities.py
index d60ef79f4..be2e99830 100644
--- a/tests/unit/test_external_ip_utilities.py
+++ b/tests/unit/test_external_ip_utilities.py
@@ -14,19 +14,20 @@
You should have received a copy of the GNU Affero General Public License
along with nucypher. If not, see .
"""
+from pathlib import Path
-from eth_utils import to_checksum_address
import pytest
-
+from eth_utils import to_checksum_address
from nucypher_core import NodeMetadata, NodeMetadataPayload
from nucypher_core.umbral import SecretKey, Signer
from nucypher.acumen.perception import FleetSensor
-from nucypher.blockchain.eth.constants import LENGTH_ECDSA_SIGNATURE_WITH_RECOVERY
from nucypher.characters.lawful import Ursula
+from nucypher.crypto.tls import generate_self_signed_certificate
from nucypher.network.exceptions import NodeSeemsToBeDown
from nucypher.network.middleware import NucypherMiddlewareClient
from nucypher.network.nodes import TEACHER_NODES
+from nucypher.network.protocols import InterfaceInfo
from nucypher.utilities.networking import (
determine_external_ip_address,
get_external_ip_from_centralized_source,
@@ -38,6 +39,7 @@ from nucypher.utilities.networking import (
from tests.constants import MOCK_IP_ADDRESS
MOCK_NETWORK = 'holodeck'
+MOCK_PORT = 1111
class Dummy: # Teacher
@@ -66,6 +68,10 @@ class Dummy: # Teacher
def rest_url(self):
return MOCK_IP_ADDRESS
+ @property
+ def rest_interface(self):
+ return InterfaceInfo(host=MOCK_IP_ADDRESS, port=MOCK_PORT)
+
def metadata(self):
signer = Signer(SecretKey.random())
@@ -79,8 +85,8 @@ class Dummy: # Teacher
verifying_key=signer.verifying_key(),
encrypting_key=SecretKey.random().public_key(),
certificate_der=b'not a certificate',
- host='127.0.0.1',
- port=1111,
+ host=MOCK_IP_ADDRESS,
+ port=MOCK_PORT,
)
return NodeMetadata(signer=signer,
payload=payload)
@@ -95,12 +101,14 @@ def mock_requests(mocker):
@pytest.fixture(autouse=True)
def mock_client(mocker):
+ cert, pk = generate_self_signed_certificate(host=MOCK_IP_ADDRESS)
+ mocker.patch.object(NucypherMiddlewareClient, 'get_certificate', return_value=(cert, Path()))
yield mocker.patch.object(NucypherMiddlewareClient, 'invoke_method', return_value=Dummy.GoodResponse)
@pytest.fixture(autouse=True)
def mock_default_teachers(mocker):
- teachers = {MOCK_NETWORK: (MOCK_IP_ADDRESS, )}
+ teachers = {MOCK_NETWORK: (f"{MOCK_IP_ADDRESS}:{MOCK_PORT}", )}
mocker.patch.dict(TEACHER_NODES, teachers, clear=True)
diff --git a/tests/utils/config.py b/tests/utils/config.py
index 4d61580d2..77846bd82 100644
--- a/tests/utils/config.py
+++ b/tests/utils/config.py
@@ -74,7 +74,8 @@ def make_ursula_test_configuration(rest_port: int = MOCK_URSULA_STARTING_PORT,
ursula_config = UrsulaConfiguration(**test_params,
rest_port=rest_port,
payment_provider=payment_provider,
- payment_network=payment_network)
+ payment_network=payment_network,
+ policy_registry=test_params['registry'])
return ursula_config
@@ -86,7 +87,8 @@ def make_alice_test_configuration(payment_provider: str = None,
payment_network = TEMPORARY_DOMAIN if not federated else None
config = AliceConfiguration(**test_params,
payment_provider=payment_provider,
- payment_network=payment_network)
+ payment_network=payment_network,
+ policy_registry=test_params['registry'])
return config
diff --git a/tests/utils/middleware.py b/tests/utils/middleware.py
index 5934f1b5e..a32c67a8c 100644
--- a/tests/utils/middleware.py
+++ b/tests/utils/middleware.py
@@ -14,19 +14,18 @@ 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 .
"""
-import time
+
+
import random
-import os
+import socket
+import time
+from pathlib import Path
import requests
-import socket
-from constant_sorrow.constants import CERTIFICATE_NOT_SAVED
from flask import Response
-
from nucypher_core import MetadataRequest, FleetStateChecksum
from nucypher.characters.lawful import Ursula
-from nucypher.config.constants import TEMPORARY_DOMAIN
from nucypher.network.middleware import NucypherMiddlewareClient, RestMiddleware
from tests.utils.ursula import MOCK_KNOWN_URSULAS_CACHE
@@ -76,9 +75,8 @@ class _TestMiddlewareClient(NucypherMiddlewareClient):
mock_client = self._get_mock_client_by_port(port)
else:
raise ValueError("You need to pass either the node or a host and port.")
-
- # We don't use certs in mock-style tests anyway.
- return node.rest_url(), CERTIFICATE_NOT_SAVED, mock_client
+ host, port = node.rest_interface.host, node.rest_interface.port
+ return host, port, mock_client
def invoke_method(self, method, url, *args, **kwargs):
_cert_location = kwargs.pop("verify") # TODO: Is this something that can be meaningfully tested?
@@ -89,6 +87,10 @@ class _TestMiddlewareClient(NucypherMiddlewareClient):
def clean_params(self, request_kwargs):
request_kwargs["query_string"] = request_kwargs.pop("params", {})
+ def get_certificate(self, port, *args, **kwargs):
+ ursula = self._get_ursula_by_port(port)
+ return ursula.certificate, Path()
+
class MockRestMiddleware(RestMiddleware):
_ursulas = None
@@ -98,11 +100,6 @@ class MockRestMiddleware(RestMiddleware):
class NotEnoughMockUrsulas(Ursula.NotEnoughUrsulas):
pass
- def get_certificate(self, host, port, timeout=3, retry_attempts: int = 3, retry_rate: int = 2,
- current_attempt: int = 0):
- ursula = self.client._get_ursula_by_port(port)
- return ursula.certificate
-
class MockRestMiddlewareForLargeFleetTests(MockRestMiddleware):
"""