Merge pull request #2873 from KPrasch/airship

Grant & Retrieve via Polygon Payments; Pre-release cleanup.
pull/2889/head
KPrasch 2022-03-24 15:25:13 -07:00 committed by GitHub
commit b89a3195d4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
36 changed files with 335 additions and 218 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,2 @@
Extend policy probationary period to 2022-6-16T23:59:59.0Z.

View File

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

View File

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

View File

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

View File

@ -14,6 +14,8 @@
You should have received a copy of the GNU Affero General Public License
along with nucypher. If not, see <https://www.gnu.org/licenses/>.
"""
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,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -16,21 +16,27 @@ along with nucypher. If not, see <https://www.gnu.org/licenses/>.
"""
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(

View File

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

View File

@ -16,9 +16,9 @@ along with nucypher. If not, see <https://www.gnu.org/licenses/>.
"""
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

View File

@ -15,15 +15,16 @@
along with nucypher. If not, see <https://www.gnu.org/licenses/>.
"""
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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -15,15 +15,14 @@ You should have received a copy of the GNU Affero General Public License
along with nucypher. If not, see <https://www.gnu.org/licenses/>.
"""
import 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

View File

@ -14,19 +14,20 @@
You should have received a copy of the GNU Affero General Public License
along with nucypher. If not, see <https://www.gnu.org/licenses/>.
"""
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)

View File

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

View File

@ -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 <https://www.gnu.org/licenses/>.
"""
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):
"""