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 # Update
RUN apt update -y && apt upgrade -y RUN apt-get update -y && apt upgrade -y
RUN apt install patch gcc libffi-dev wget git -y RUN apt-get install patch gcc libffi-dev wget git -y
WORKDIR /code WORKDIR /code
COPY . /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') BOOK_PATH = Path(os.getenv('FINNEGANS_WAKE_PATH') or 'finnegans-wake-excerpt.txt')
# Twisted Logger # Twisted Logger
GlobalLoggerSettings.set_log_level(log_level_name='debug') GlobalLoggerSettings.set_log_level(log_level_name='info')
GlobalLoggerSettings.start_console_logging() GlobalLoggerSettings.start_console_logging()
# if your ursulas are NOT running on your current host, # if your ursulas are NOT running on your current host,

View File

@ -16,27 +16,26 @@
""" """
from getpass import getpass
import datetime import datetime
import maya
import os import os
from getpass import getpass
from pathlib import Path from pathlib import Path
from web3.main import Web3
import maya
from nucypher.blockchain.eth.signers.base import Signer 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.characters.lawful import Enrico as Enrico
from nucypher.crypto.powers import SigningPower, DecryptingPower from nucypher.crypto.powers import SigningPower, DecryptingPower
from nucypher.policy.payment import SubscriptionManagerPayment
from nucypher.utilities.ethereum import connect_web3_provider from nucypher.utilities.ethereum import connect_web3_provider
from nucypher.utilities.logging import GlobalLoggerSettings from nucypher.utilities.logging import GlobalLoggerSettings
###################### ######################
# Boring setup stuff # # Boring setup stuff #
###################### ######################
GlobalLoggerSettings.set_log_level(log_level_name='debug') GlobalLoggerSettings.set_log_level(log_level_name='info')
GlobalLoggerSettings.start_console_logging() GlobalLoggerSettings.start_console_logging()
BOOK_PATH = Path('finnegans-wake-excerpt.txt') BOOK_PATH = Path('finnegans-wake-excerpt.txt')
@ -62,9 +61,10 @@ except KeyError:
# NuCypher Network # # NuCypher Network #
#################### ####################
L1_TESTNET = 'lynx' L1_TESTNET = 'ibex'
L2_TESTNET = 'mumbai' # TODO: Needs name different than the network name L2_TESTNET = 'mumbai' # TODO: Needs name different than the network name
##################### #####################
# Bob the BUIDLer ## # 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]}: ") 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) wallet.unlock_account(account=ALICE_ADDRESS, password=password)
payment_method = SubscriptionManagerPayment(
network='mumbai',
eth_provider=L2_PROVIDER
)
# This is Alice. # This is Alice.
alice = Alice( alice = Alice(
checksum_address=ALICE_ADDRESS, checksum_address=ALICE_ADDRESS,
signer=wallet, signer=wallet,
domain=L1_TESTNET, domain=L1_TESTNET,
payment_network=L2_TESTNET,
payment_provider=L2_PROVIDER,
eth_provider_uri=L1_PROVIDER, eth_provider_uri=L1_PROVIDER,
payment_method=payment_method
) )
# Alice puts her public key somewhere for Bob to find later... # 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. # Alice can get the policy's public key even before creating the policy.
label = b"secret/files/42" 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. # can be shared with any Bob that Alice grants access.
# Alice already knows Bob's public keys from a side-channel. # 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. # These are the policy details for bob.
# In this example bob will be granted access for 1 day, # In this example bob will be granted access for 1 day,
# trusting 2 of 3 nodes paying each of them 50 gwei per period. # trusting 2 of 3 nodes paying each of them 50 gwei per period.
expiration = maya.now() + datetime.timedelta(days=1) expiration = maya.now() + datetime.timedelta(days=1)
threshold, shares = 2, 3 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... # 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. # ...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. # We show that indeed this is the passage originally encrypted by Enrico.
assert plaintext == cleartexts[0] assert plaintext == cleartexts[0]
print(cleartexts)
bob.disenchant() bob.disenchant()

View File

@ -36,7 +36,7 @@ from nucypher.utilities.logging import GlobalLoggerSettings
# Boring setup stuff # # Boring setup stuff #
###################### ######################
GlobalLoggerSettings.set_log_level(log_level_name='debug') GlobalLoggerSettings.set_log_level(log_level_name='info')
GlobalLoggerSettings.start_console_logging() GlobalLoggerSettings.start_console_logging()
BOOK_PATH = Path('finnegans-wake-excerpt.txt') 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): class BlockchainPolicyAuthor(NucypherTokenActor):
"""Alice base class for blockchain operations, mocking up new policies!""" """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) 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): def create_policy(self, *args, **kwargs):
"""Hence the name, a BlockchainPolicyAuthor can create a BlockchainPolicy with themself as the author.""" """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): contract_version: Optional[str] = None):
self.log = Logger(self.__class__.__name__) 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) 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'] transaction_gas = EthereumContractAgent.DEFAULT_TRANSACTION_GAS_LIMITS['default']
self.transaction_gas = transaction_gas self.transaction_gas = transaction_gas
self.log.info("Initialized new {} for {} with {} and {}".format(self.__class__.__name__, self.log.info("Initialized new {} for {} with {} and {}".format(
self.__class__.__name__,
self.contract.address, self.contract.address,
self.blockchain.eth_provider_uri, self.blockchain.eth_provider_uri,
self.registry_str)) str(self.registry)
))
def __repr__(self) -> str: def __repr__(self) -> str:
class_name = self.__class__.__name__ class_name = self.__class__.__name__
r = "{}(registry={}, contract={})" 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: def __eq__(self, other: Any) -> bool:
return bool(self.contract.address == other.contract.address) 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.base import Character, Learner
from nucypher.characters.control.interfaces import AliceInterface, BobInterface, EnricoInterface from nucypher.characters.control.interfaces import AliceInterface, BobInterface, EnricoInterface
from nucypher.cli.processes import UrsulaCommandProtocol 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.controllers import WebController
from nucypher.control.emitters import StdoutEmitter from nucypher.control.emitters import StdoutEmitter
from nucypher.crypto.keypairs import HostingKeypair from nucypher.crypto.keypairs import HostingKeypair
@ -169,7 +169,8 @@ class Alice(Character, BlockchainPolicyAuthor):
BlockchainPolicyAuthor.__init__(self, BlockchainPolicyAuthor.__init__(self,
domain=self.domain, domain=self.domain,
transacting_power=self.transacting_power, transacting_power=self.transacting_power,
registry=self.registry) registry=self.registry,
eth_provider_uri=eth_provider_uri)
self.log = Logger(self.__class__.__name__) self.log = Logger(self.__class__.__name__)
if is_me: if is_me:
@ -572,6 +573,7 @@ class Bob(Character):
if not publisher_verifying_key: if not publisher_verifying_key:
publisher_verifying_key = alice_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. # A small optimization to avoid multiple treasure map decryptions.
map_hash = hash(bytes(encrypted_treasure_map)) map_hash = hash(bytes(encrypted_treasure_map))
@ -715,7 +717,7 @@ class Ursula(Teacher, Character, Operator):
known_nodes: Iterable[Teacher] = None, known_nodes: Iterable[Teacher] = None,
**character_kwargs **character_kwargs
) -> None: ):
Character.__init__(self, Character.__init__(self,
is_me=is_me, is_me=is_me,
@ -1075,15 +1077,9 @@ class Ursula(Teacher, Character, Operator):
def from_rest_url(cls, def from_rest_url(cls,
network_middleware: RestMiddleware, network_middleware: RestMiddleware,
host: str, host: str,
port: int, port: int):
certificate_filepath, response_data = network_middleware.client.node_information(host, port)
*args, **kwargs
):
response_data = network_middleware.client.node_information(host, port,
certificate_filepath=certificate_filepath)
stranger_ursula_from_public_keys = cls.from_metadata_bytes(response_data) stranger_ursula_from_public_keys = cls.from_metadata_bytes(response_data)
return stranger_ursula_from_public_keys return stranger_ursula_from_public_keys
@classmethod @classmethod
@ -1134,7 +1130,7 @@ class Ursula(Teacher, Character, Operator):
except NodeSeemsToBeDown as e: except NodeSeemsToBeDown as e:
log = Logger(cls.__name__) log = Logger(cls.__name__)
log.warn( 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) time.sleep(interval)
return __attempt(attempt=attempt + 1) return __attempt(attempt=attempt + 1)
else: else:
@ -1149,8 +1145,6 @@ class Ursula(Teacher, Character, Operator):
minimum_stake: int = 0, minimum_stake: int = 0,
registry: BaseContractRegistry = None, registry: BaseContractRegistry = None,
network_middleware: RestMiddleware = None, network_middleware: RestMiddleware = None,
*args,
**kwargs
) -> Union['Ursula', 'NodeSprout']: ) -> Union['Ursula', 'NodeSprout']:
if network_middleware is None: if network_middleware is None:
@ -1161,25 +1155,18 @@ class Ursula(Teacher, Character, Operator):
# Fetch the hosts TLS certificate and read the common name # Fetch the hosts TLS certificate and read the common name
try: 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: except NodeSeemsToBeDown as e:
e.args += (f"While trying to load seednode {seed_uri}",) e.args += (f"While trying to load seednode {seed_uri}",)
e.crash_right_now = True e.crash_right_now = True
raise raise
real_host = certificate.subject.get_attributes_for_oid(NameOID.COMMON_NAME)[0].value 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 # Load the host as a potential seed node
potential_seed_node = cls.from_rest_url( potential_seed_node = cls.from_rest_url(
host=real_host, host=real_host,
port=port, port=port,
network_middleware=network_middleware, network_middleware=network_middleware,
certificate_filepath=temp_certificate_filepath,
*args,
**kwargs
) )
# Check the node's stake (optional) # Check the node's stake (optional)
@ -1189,8 +1176,6 @@ class Ursula(Teacher, Character, Operator):
if seednode_stake < minimum_stake: if seednode_stake < minimum_stake:
raise Learner.NotATeacher(f"{staking_provider_address} is staking less than the specified minimum stake value ({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 return potential_seed_node
@classmethod @classmethod

View File

@ -14,6 +14,8 @@
You should have received a copy of the GNU Affero General Public License You should have received a copy of the GNU Affero General Public License
along with nucypher. If not, see <https://www.gnu.org/licenses/>. along with nucypher. If not, see <https://www.gnu.org/licenses/>.
""" """
from pathlib import Path from pathlib import Path
import click import click
@ -54,7 +56,11 @@ from nucypher.cli.options import (
option_teacher_uri, option_teacher_uri,
option_lonely, option_lonely,
option_max_gas_price, 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.painting.help import paint_new_installation_help
from nucypher.cli.types import EIP55_CHECKSUM_ADDRESS, NETWORK_PORT, OPERATOR_IP from nucypher.cli.types import EIP55_CHECKSUM_ADDRESS, NETWORK_PORT, OPERATOR_IP
@ -80,6 +86,7 @@ class UrsulaConfigOptions:
db_filepath: Path, db_filepath: Path,
network: str, network: str,
registry_filepath: Path, registry_filepath: Path,
policy_registry_filepath: Path,
dev: bool, dev: bool,
poa: bool, poa: bool,
light: bool, light: bool,
@ -94,9 +101,9 @@ class UrsulaConfigOptions:
): ):
if federated_only: if federated_only:
if registry_filepath: if registry_filepath or policy_registry_filepath:
raise click.BadOptionUsage(option_name="--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.eth_provider_uri = eth_provider_uri
self.signer_uri = signer_uri self.signer_uri = signer_uri
@ -107,6 +114,7 @@ class UrsulaConfigOptions:
self.db_filepath = db_filepath self.db_filepath = db_filepath
self.domain = network self.domain = network
self.registry_filepath = registry_filepath self.registry_filepath = registry_filepath
self.policy_registry_filepath = policy_registry_filepath
self.dev = dev self.dev = dev
self.poa = poa self.poa = poa
self.light = light self.light = light
@ -127,6 +135,7 @@ class UrsulaConfigOptions:
poa=self.poa, poa=self.poa,
light=self.light, light=self.light,
registry_filepath=self.registry_filepath, registry_filepath=self.registry_filepath,
policy_registry_filepath=self.policy_registry_filepath,
eth_provider_uri=self.eth_provider_uri, eth_provider_uri=self.eth_provider_uri,
signer_uri=self.signer_uri, signer_uri=self.signer_uri,
gas_strategy=self.gas_strategy, gas_strategy=self.gas_strategy,
@ -152,6 +161,7 @@ class UrsulaConfigOptions:
filepath=config_file, filepath=config_file,
domain=self.domain, domain=self.domain,
registry_filepath=self.registry_filepath, registry_filepath=self.registry_filepath,
policy_registry_filepath=self.policy_registry_filepath,
eth_provider_uri=self.eth_provider_uri, eth_provider_uri=self.eth_provider_uri,
signer_uri=self.signer_uri, signer_uri=self.signer_uri,
gas_strategy=self.gas_strategy, gas_strategy=self.gas_strategy,
@ -200,6 +210,7 @@ class UrsulaConfigOptions:
federated_only=self.federated_only, federated_only=self.federated_only,
operator_address=self.operator_address, operator_address=self.operator_address,
registry_filepath=self.registry_filepath, registry_filepath=self.registry_filepath,
policy_registry_filepath=self.policy_registry_filepath,
eth_provider_uri=self.eth_provider_uri, eth_provider_uri=self.eth_provider_uri,
signer_uri=self.signer_uri, signer_uri=self.signer_uri,
gas_strategy=self.gas_strategy, gas_strategy=self.gas_strategy,
@ -220,6 +231,7 @@ class UrsulaConfigOptions:
federated_only=self.federated_only, federated_only=self.federated_only,
operator_address=self.operator_address, operator_address=self.operator_address,
registry_filepath=self.registry_filepath, registry_filepath=self.registry_filepath,
policy_registry_filepath=self.policy_registry_filepath,
eth_provider_uri=self.eth_provider_uri, eth_provider_uri=self.eth_provider_uri,
signer_uri=self.signer_uri, signer_uri=self.signer_uri,
gas_strategy=self.gas_strategy, gas_strategy=self.gas_strategy,
@ -250,6 +262,7 @@ group_config_options = group_options(
db_filepath=option_db_filepath, db_filepath=option_db_filepath,
network=option_network(), network=option_network(),
registry_filepath=option_registry_filepath, registry_filepath=option_registry_filepath,
policy_registry_filepath=option_policy_registry_filepath,
poa=option_poa, poa=option_poa,
light=option_light, light=option_light,
dev=option_dev, 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_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_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_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_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_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) 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: if character_config.federated_only:
emitter.message(FEDERATED_WARNING, color='yellow') 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 return CHARACTER

View File

@ -408,9 +408,11 @@ class CharacterConfiguration(BaseConfiguration):
payment_provider: str = None, payment_provider: str = None,
payment_network: str = None, payment_network: str = None,
# Registry # Registries
registry: BaseContractRegistry = None, registry: BaseContractRegistry = None,
registry_filepath: Optional[Path] = None, registry_filepath: Optional[Path] = None,
policy_registry: BaseContractRegistry = None,
policy_registry_filepath: Optional[Path] = None,
# Deployed Operators # Deployed Operators
worker_data: dict = None worker_data: dict = None
@ -444,6 +446,9 @@ class CharacterConfiguration(BaseConfiguration):
self.registry = registry or NO_BLOCKCHAIN_CONNECTION.bool_value(False) self.registry = registry or NO_BLOCKCHAIN_CONNECTION.bool_value(False)
self.registry_filepath = registry_filepath or UNINITIALIZED_CONFIGURATION 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 # Blockchain
self.poa = poa self.poa = poa
self.is_light = light self.is_light = light
@ -495,6 +500,7 @@ class CharacterConfiguration(BaseConfiguration):
self.is_light = False self.is_light = False
self.eth_provider_uri = None self.eth_provider_uri = None
self.registry_filepath = None self.registry_filepath = None
self.policy_registry_filepath = None
self.gas_strategy = None self.gas_strategy = None
self.max_gas_price = None self.max_gas_price = None
@ -533,14 +539,28 @@ class CharacterConfiguration(BaseConfiguration):
self.testnet = self.domain != NetworksInventory.MAINNET self.testnet = self.domain != NetworksInventory.MAINNET
self.signer = Signer.from_signer_uri(self.signer_uri, testnet=self.testnet) self.signer = Signer.from_signer_uri(self.signer_uri, testnet=self.testnet)
# Onchain Payments #
# TODO: Enforce this for Ursula/Alice but not Bob? # 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: # if not payment_provider:
# raise self.ConfigurationError("payment provider is required.") # raise self.ConfigurationError("payment provider is required.")
self.payment_method = payment_method or self.DEFAULT_PAYMENT_METHOD self.payment_method = payment_method or self.DEFAULT_PAYMENT_METHOD
self.payment_network = payment_network or self.DEFAULT_PAYMENT_NETWORK 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 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: if dev_mode:
self.__temp_dir = UNINITIALIZED_CONFIGURATION self.__temp_dir = UNINITIALIZED_CONFIGURATION
self._setup_node_storage() self._setup_node_storage()
@ -618,12 +638,21 @@ class CharacterConfiguration(BaseConfiguration):
return self.__dev_mode return self.__dev_mode
def _setup_node_storage(self, node_storage=None) -> None: def _setup_node_storage(self, node_storage=None) -> None:
if self.dev_mode: # TODO: Disables node metadata persistence..
node_storage = ForgetfulNodeStorage(registry=self.registry, federated_only=self.federated_only) # if self.dev_mode:
elif not node_storage: # node_storage = ForgetfulNodeStorage(registry=self.registry, federated_only=self.federated_only)
node_storage = LocalFileBasedNodeStorage(registry=self.registry,
# TODO: Forcibly clears the filesystem of any stored node metadata and certificates...
local_node_storage = LocalFileBasedNodeStorage(
registry=self.registry,
config_root=self.config_root, config_root=self.config_root,
federated_only=self.federated_only) 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 self.node_storage = node_storage
def forget_nodes(self) -> None: def forget_nodes(self) -> None:
@ -869,7 +898,8 @@ class CharacterConfiguration(BaseConfiguration):
if payment_class.ONCHAIN: if payment_class.ONCHAIN:
# on-chain payment strategies require a blockchain connection # on-chain payment strategies require a blockchain connection
payment_strategy = payment_class(network=self.payment_network, payment_strategy = payment_class(network=self.payment_network,
provider=self.payment_provider) eth_provider=self.payment_provider,
registry=self.policy_registry)
else: else:
payment_strategy = payment_class() payment_strategy = payment_class()
return payment_strategy return payment_strategy

View File

@ -29,7 +29,7 @@ import nucypher
# Environment variables # Environment variables
NUCYPHER_ENVVAR_KEYSTORE_PASSWORD = "NUCYPHER_KEYSTORE_PASSWORD" NUCYPHER_ENVVAR_KEYSTORE_PASSWORD = "NUCYPHER_KEYSTORE_PASSWORD"
NUCYPHER_ENVVAR_OPERATOR_ADDRESS = "NUCYPHER_OPERATOR_ADDRESS" 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_STAKING_PROVIDER_ETH_PASSWORD = "NUCYPHER_STAKING_PROVIDER_ETH_PASSWORD"
NUCYPHER_ENVVAR_ALICE_ETH_PASSWORD = "NUCYPHER_ALICE_ETH_PASSWORD" NUCYPHER_ENVVAR_ALICE_ETH_PASSWORD = "NUCYPHER_ALICE_ETH_PASSWORD"
NUCYPHER_ENVVAR_BOB_ETH_PASSWORD = "NUCYPHER_BOB_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' NUCYPHER_EVENTS_THROTTLE_MAX_BLOCKS = 'NUCYPHER_EVENTS_THROTTLE_MAX_BLOCKS'
# Probationary period # 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 pass
def __init__(self, def __init__(self,
federated_only: bool, # TODO# 466 federated_only: bool = False, # TODO# 466
character_class=None, character_class=None,
registry: BaseContractRegistry = None, registry: BaseContractRegistry = None,
) -> None: ) -> None:

View File

@ -18,8 +18,10 @@
import requests import requests
import socket import socket
NodeSeemsToBeDown = (requests.exceptions.ConnectionError, NodeSeemsToBeDown = (
requests.exceptions.ConnectionError,
requests.exceptions.ReadTimeout, requests.exceptions.ReadTimeout,
requests.exceptions.ConnectTimeout, requests.exceptions.ConnectTimeout,
socket.gaierror, socket.gaierror,
ConnectionRefusedError) 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 socket
import ssl import ssl
import time import time
from http import HTTPStatus
from pathlib import Path
from typing import Optional, Tuple
from typing import Sequence from typing import Sequence
import requests import requests
from constant_sorrow.constants import EXEMPT_FROM_VERIFICATION
from nucypher_core import MetadataRequest, FleetStateChecksum, NodeMetadata
from constant_sorrow.constants import CERTIFICATE_NOT_SAVED, EXEMPT_FROM_VERIFICATION
from cryptography import x509 from cryptography import x509
from cryptography.hazmat.backends import default_backend 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 from nucypher.utilities.logging import Logger
SSL_LOGGER = Logger('ssl-middleware')
EXEMPT_FROM_VERIFICATION.bool_value(False) EXEMPT_FROM_VERIFICATION.bool_value(False)
@ -38,9 +44,46 @@ class NucypherMiddlewareClient:
library = requests library = requests
timeout = 1.2 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.registry = registry
self.eth_provider_uri = eth_provider_uri 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 @staticmethod
def response_cleaner(response): def response_cleaner(response):
@ -63,15 +106,10 @@ class NucypherMiddlewareClient:
if node: if node:
if any((host, port)): if any((host, port)):
raise ValueError("Don't pass host and port if you are passing the node.") raise ValueError("Don't pass host and port if you are passing the node.")
host = node.rest_url() host, port = node.rest_interface.host, node.rest_interface.port
certificate_filepath = node.certificate_filepath elif not (host and port):
elif all((host, port)):
host = f"{host}:{port}"
certificate_filepath = CERTIFICATE_NOT_SAVED
else:
raise ValueError("You need to pass either the node or a host and port.") raise ValueError("You need to pass either the node or a host and port.")
return host, port, self.library
return host, certificate_filepath, self.library
def invoke_method(self, method, url, *args, **kwargs): def invoke_method(self, method, url, *args, **kwargs):
self.clean_params(kwargs) self.clean_params(kwargs)
@ -86,13 +124,12 @@ class NucypherMiddlewareClient:
No cleaning needed. 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. # 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, response = self.get(node_or_sprout=EXEMPT_FROM_VERIFICATION,
host=host, port=port, host=host, port=port,
path="public_information", path="public_information",
timeout=2, timeout=2)
certificate_filepath=certificate_filepath)
return response.content return response.content
def __getattr__(self, method_name): def __getattr__(self, method_name):
@ -104,38 +141,44 @@ class NucypherMiddlewareClient:
node_or_sprout=None, node_or_sprout=None,
host=None, host=None,
port=None, port=None,
certificate_filepath=None,
*args, **kwargs): *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) method = getattr(http_client, method_name)
url = f"https://{host}/{path}" # Fetch SSL certificate
response = self.invoke_method(method, url, verify=certificate_filepath, *args, **kwargs) 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) cleaned_response = self.response_cleaner(response)
if cleaned_response.status_code >= 300: if cleaned_response.status_code >= 300:
if cleaned_response.status_code == HTTPStatus.BAD_REQUEST: if cleaned_response.status_code == HTTPStatus.BAD_REQUEST:
raise RestMiddleware.BadRequest(reason=cleaned_response.json) raise RestMiddleware.BadRequest(reason=cleaned_response.json)
elif cleaned_response.status_code == HTTPStatus.NOT_FOUND: elif cleaned_response.status_code == HTTPStatus.NOT_FOUND:
m = f"While trying to {method_name} {args} ({kwargs}), server 404'd. Response: {cleaned_response.content}" m = f"While trying to {method_name} {args} ({kwargs}), server 404'd. Response: {cleaned_response.content}"
raise RestMiddleware.NotFound(m) raise RestMiddleware.NotFound(m)
elif cleaned_response.status_code == HTTPStatus.PAYMENT_REQUIRED: elif cleaned_response.status_code == HTTPStatus.PAYMENT_REQUIRED:
# TODO: Use this as a hook to prompt Bob's payment for policy sponsorship # TODO: Use this as a hook to prompt Bob's payment for policy sponsorship
# https://getyarn.io/yarn-clip/ce0d37ba-4984-4210-9a40-c9c9859a3164 # https://getyarn.io/yarn-clip/ce0d37ba-4984-4210-9a40-c9c9859a3164
raise RestMiddleware.PaymentRequired(cleaned_response.content) raise RestMiddleware.PaymentRequired(cleaned_response.content)
elif cleaned_response.status_code == HTTPStatus.FORBIDDEN: elif cleaned_response.status_code == HTTPStatus.FORBIDDEN:
raise RestMiddleware.Unauthorized(cleaned_response.content) raise RestMiddleware.Unauthorized(cleaned_response.content)
else: else:
raise RestMiddleware.UnexpectedResponse(cleaned_response.content, status=cleaned_response.status_code) raise RestMiddleware.UnexpectedResponse(cleaned_response.content, status=cleaned_response.status_code)
return cleaned_response return cleaned_response
return method_wrapper return method_wrapper
@ -152,6 +195,10 @@ class RestMiddleware:
_client_class = NucypherMiddlewareClient _client_class = NucypherMiddlewareClient
class Unreachable(Exception):
def __init__(self, message, *args, **kwargs):
super().__init__(message, *args, **kwargs)
class UnexpectedResponse(Exception): class UnexpectedResponse(Exception):
def __init__(self, message, status, *args, **kwargs): def __init__(self, message, status, *args, **kwargs):
super().__init__(message, *args, **kwargs) super().__init__(message, *args, **kwargs)
@ -179,33 +226,6 @@ class RestMiddleware:
def __init__(self, registry=None, eth_provider_uri: str = None): def __init__(self, registry=None, eth_provider_uri: str = None):
self.client = self._client_class(registry=registry, eth_provider_uri=eth_provider_uri) 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): def request_revocation(self, ursula, revocation):
# TODO: Implement offchain revocation #2787 # TODO: Implement offchain revocation #2787
response = self.client.post( response = self.client.post(

View File

@ -24,7 +24,6 @@ from typing import Callable, List, Optional, Set, Tuple, Union
import maya import maya
import requests import requests
from constant_sorrow import constant_or_bytes
from constant_sorrow.constants import ( from constant_sorrow.constants import (
CERTIFICATE_NOT_SAVED, CERTIFICATE_NOT_SAVED,
FLEET_STATES_MATCH, FLEET_STATES_MATCH,
@ -32,19 +31,18 @@ from constant_sorrow.constants import (
NO_STORAGE_AVAILABLE, NO_STORAGE_AVAILABLE,
RELAX, RELAX,
) )
from cryptography.x509 import Certificate, load_der_x509_certificate
from cryptography.hazmat.backends import default_backend 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 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 requests.exceptions import SSLError
from twisted.internet import reactor, task from twisted.internet import reactor, task
from twisted.internet.defer import Deferred 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.nicknames import Nickname
from nucypher.acumen.perception import FleetSensor from nucypher.acumen.perception import FleetSensor
from nucypher.blockchain.economics import EconomicsFactory
from nucypher.blockchain.eth.agents import ContractAgency, PREApplicationAgent from nucypher.blockchain.eth.agents import ContractAgency, PREApplicationAgent
from nucypher.blockchain.eth.constants import NULL_ADDRESS from nucypher.blockchain.eth.constants import NULL_ADDRESS
from nucypher.blockchain.eth.networks import NetworksInventory from nucypher.blockchain.eth.networks import NetworksInventory
@ -58,7 +56,6 @@ from nucypher.crypto.powers import (
SigningPower, SigningPower,
) )
from nucypher.crypto.signing import SignatureStamp, InvalidSignature 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.exceptions import NodeSeemsToBeDown
from nucypher.network.middleware import RestMiddleware from nucypher.network.middleware import RestMiddleware
from nucypher.network.protocols import SuspiciousActivity, InterfaceInfo 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 # TODO: Bucket this node as having bad TLS info - maybe it's an update that hasn't fully propagated? 567
return False return False
except NodeSeemsToBeDown: except RestMiddleware.Unreachable:
self.log.info("No Response while trying to verify node {}|{}".format(node.rest_interface, node)) 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 # TODO: Bucket this node as "ghost" or something: somebody else knows about it, but we can't get to it. 567
return False return False
@ -805,15 +802,14 @@ class Learner:
# These except clauses apply to the current_teacher itself, not the learned-about nodes. # These except clauses apply to the current_teacher itself, not the learned-about nodes.
except NodeSeemsToBeDown as e: except NodeSeemsToBeDown as e:
unresponsive_nodes.add(current_teacher) unresponsive_nodes.add(current_teacher)
self.log.info( self.log.info(f"Teacher {current_teacher.seed_node_metadata(as_teacher_uri=True)} is unreachable: {e}.")
f"Teacher {str(current_teacher)} is perhaps down:{e}.") # FIXME: This was printing the node bytestring. Is this really necessary? #1712
return return
except current_teacher.InvalidNode as e: except current_teacher.InvalidNode as e:
# Ugh. The teacher is invalid. Rough. # Ugh. The teacher is invalid. Rough.
# TODO: Bucket separately and report. # TODO: Bucket separately and report.
unresponsive_nodes.add(current_teacher) # This does nothing. unresponsive_nodes.add(current_teacher) # This does nothing.
self.known_nodes.mark_as(current_teacher.InvalidNode, current_teacher) 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 # TODO (#567): bucket the node as suspicious
return return
except RuntimeError as e: except RuntimeError as e:
@ -918,8 +914,8 @@ class Learner:
# except sprout.Invalidsprout: # except sprout.Invalidsprout:
# self.log.warn(sprout.invalid_metadata_message.format(sprout)) # self.log.warn(sprout.invalid_metadata_message.format(sprout))
except InvalidNodeCertificate as e: except NodeSeemsToBeDown as e:
message = f"Discovered sprout with invalid node certificate: {sprout}. Full error: {e.__str__()} " message = f"Node is unreachable: {sprout}. Full error: {e.__str__()} "
self.log.warn(message) self.log.warn(message)
except SuspiciousActivity: except SuspiciousActivity:
@ -973,7 +969,7 @@ class Teacher:
# Assume unverified # Assume unverified
self.verified_stamp = False self.verified_stamp = False
self.verified_worker = False self.verified_operator = False
self.verified_metadata = False self.verified_metadata = False
self.verified_node = 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 is_staking = application_agent.is_authorized(staking_provider=self.checksum_address) # checksum address here is staking provider
return is_staking 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 # Federated
if self.federated_only: if self.federated_only:
@ -1068,23 +1064,28 @@ class Teacher:
# Try to derive the worker address if it hasn't been derived yet. # Try to derive the worker address if it hasn't been derived yet.
try: try:
operator_address = self.operator_address # TODO: This is overtly implicit
_operator_address = self.operator_address
except Exception as e: except Exception as e:
raise self.InvalidOperatorSignature(str(e)) from 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 # On-chain staking check, if registry is present
if registry: if registry:
if not self._operator_is_bonded(registry=registry): # <-- Blockchain CALL 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}" message = f"Operator {self.operator_address} is not bonded to staking provider {self.checksum_address}"
self.log.debug(message) self.log.debug(message)
raise self.UnbondedOperator(message) raise self.UnbondedOperator(message)
if self._staking_provider_is_really_staking(registry=registry, eth_provider_uri=eth_provider_uri): # <-- Blockchain CALL 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: else:
raise self.NotStaking(f"{self.checksum_address} is not staking") 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: def validate_metadata_signature(self) -> bool:
"""Checks that the interface info is valid for this node's canonical address.""" """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 # Offline check of valid stamp signature by worker
try: 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: except self.WrongMode:
if bool(registry): if bool(registry):
raise raise
@ -1137,7 +1138,7 @@ class Teacher:
self.verified_metadata = False self.verified_metadata = False
self.verified_node = False self.verified_node = False
self.verified_stamp = False self.verified_stamp = False
self.verified_worker = False self.verified_operator = False
if self.verified_node: if self.verified_node:
return True return True
@ -1156,8 +1157,7 @@ class Teacher:
certificate_filepath = self.certificate_filepath certificate_filepath = self.certificate_filepath
response_data = network_middleware_client.node_information(host=self.rest_interface.host, response_data = network_middleware_client.node_information(host=self.rest_interface.host,
port=self.rest_interface.port, port=self.rest_interface.port)
certificate_filepath=certificate_filepath)
try: try:
sprout = self.from_metadata_bytes(response_data) 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 uuid
import weakref import weakref
from http import HTTPStatus
from pathlib import Path from pathlib import Path
from typing import Tuple from typing import Tuple
@ -27,7 +27,6 @@ from constant_sorrow.constants import RELAX
from flask import Flask, Response, jsonify, request from flask import Flask, Response, jsonify, request
from mako import exceptions as mako_exceptions from mako import exceptions as mako_exceptions
from mako.template import Template from mako.template import Template
from nucypher_core import ( from nucypher_core import (
ReencryptionRequest, ReencryptionRequest,
RevocationOrder, RevocationOrder,
@ -42,8 +41,8 @@ from nucypher.crypto.signing import InvalidSignature
from nucypher.datastore.datastore import Datastore from nucypher.datastore.datastore import Datastore
from nucypher.datastore.models import ReencryptionRequest as ReencryptionRequestModel from nucypher.datastore.models import ReencryptionRequest as ReencryptionRequestModel
from nucypher.network.exceptions import NodeSeemsToBeDown from nucypher.network.exceptions import NodeSeemsToBeDown
from nucypher.network.protocols import InterfaceInfo
from nucypher.network.nodes import NodeSprout from nucypher.network.nodes import NodeSprout
from nucypher.network.protocols import InterfaceInfo
from nucypher.utilities.logging import Logger from nucypher.utilities.logging import Logger
HERE = BASE_DIR = Path(__file__).parent 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 :-( # TODO: Avoid circular imports :-(
from nucypher.characters.lawful import Alice, Bob, Ursula from nucypher.characters.lawful import Alice, Bob, Ursula
from nucypher.policy.policies import Policy
_alice_class = Alice _alice_class = Alice
_bob_class = Bob _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 # TODO: Evaluate multiple reencryption prerequisites & enforce policy expiration
paid = this_node.payment_method.verify(payee=this_node.checksum_address, request=reenc_request) paid = this_node.payment_method.verify(payee=this_node.checksum_address, request=reenc_request)
if not paid: 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) return Response(message, status=HTTPStatus.PAYMENT_REQUIRED)
# Re-encrypt # Re-encrypt
@ -255,13 +253,9 @@ def _make_rest_app(datastore: Datastore, this_node, log: Logger) -> Flask:
# Make a Sandwich # Make a Sandwich
try: 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( requesting_ursula_metadata = this_node.network_middleware.client.node_information(
host=initiator_address, host=initiator_address,
port=initiator_port, port=initiator_port,
certificate_filepath=certificate_filepath
) )
except NodeSeemsToBeDown: except NodeSeemsToBeDown:
return Response({'error': 'Unreachable node'}, status=HTTPStatus.BAD_REQUEST) # ... toasted 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/>. along with nucypher. If not, see <https://www.gnu.org/licenses/>.
""" """
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from typing import Optional, NamedTuple, Dict from typing import Optional, NamedTuple, Dict
import maya import maya
from nucypher_core import ReencryptionRequest 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.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 from nucypher.policy.policies import BlockchainPolicy, Policy
@ -91,11 +92,17 @@ class ContractPayment(PaymentMethod, ABC):
rate: Wei rate: Wei
value: 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) super().__init__(*args, **kwargs)
self.provider = provider self.provider = eth_provider
self.network = network 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 self.__agent = None # delay blockchain/registry reads until later
@property @property

View File

@ -17,11 +17,12 @@
import random import random
import requests
from ipaddress import ip_address from ipaddress import ip_address
from requests.exceptions import RequestException, HTTPError
from typing import Union, Optional from typing import Union, Optional
import requests
from requests.exceptions import RequestException, HTTPError
from nucypher.acumen.perception import FleetSensor from nucypher.acumen.perception import FleetSensor
from nucypher.blockchain.eth.registry import BaseContractRegistry from nucypher.blockchain.eth.registry import BaseContractRegistry
from nucypher.config.storages import LocalFileBasedNodeStorage 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 # Get agents using same registry instance
application_agent_1 = ContractAgency.get_agent(PREApplicationAgent, registry=test_registry) application_agent_1 = ContractAgency.get_agent(PREApplicationAgent, registry=test_registry)
application_agent_2 = 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 assert application_agent_2 is application_agent_1
# Same content but different classes of registries # Same content but different classes of registries
application_agent_2 = ContractAgency.get_agent(PREApplicationAgent, registry=agency_local_registry) 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 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): 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 blockchain_alice.payment_method = payment_method
policy = blockchain_alice.grant(bob=blockchain_bob, policy = blockchain_alice.grant(bob=blockchain_bob,
label=os.urandom(16), 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) assert len(ursulas) == len(staking_providers)
for ursula in ursulas: for ursula in ursulas:
ursula.validate_worker(registry=test_registry) ursula.validate_operator(registry=test_registry)
assert ursula.verified_worker assert ursula.verified_operator
def test_blockchain_ursula_substantiates_stamp(blockchain_ursulas): 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 # This Ursula does not yet have a verified stamp
first_ursula.verified_stamp = False first_ursula.verified_stamp = False
first_ursula.validate_worker() first_ursula.validate_operator()
# ...but now it's verified. # ...but now it's verified.
assert first_ursula.verified_stamp 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. # But the actual handshake proves him wrong.
message = "Wallet address swapped out. It appears that someone is trying to defraud this node." message = "Wallet address swapped out. It appears that someone is trying to defraud this node."
with pytest.raises(vladimir.InvalidNode, match=message): 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 # 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 # Files and Directories
assert custom_filepath.is_dir(), 'Configuration file does not exist' assert custom_filepath.is_dir(), 'Configuration file does not exist'
assert (custom_filepath / 'keystore').is_dir(), 'Keystore 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() custom_config_filepath = custom_filepath / AliceConfiguration.generate_filename()
assert custom_config_filepath.is_file(), 'Configuration file does not exist' 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 # Files and Directories
assert custom_filepath.is_dir(), 'Configuration file does not exist' assert custom_filepath.is_dir(), 'Configuration file does not exist'
assert (custom_filepath / 'keystore').is_dir(), 'Keystore 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() custom_config_filepath = custom_filepath / BobConfiguration.generate_filename()
assert custom_config_filepath.is_file(), 'Configuration file does not exist' 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 # Files and Directories
assert custom_filepath.is_dir(), 'Configuration file does not exist' assert custom_filepath.is_dir(), 'Configuration file does not exist'
assert (custom_filepath / 'keystore').is_dir(), 'Keystore 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) @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): 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 # Ensure configuration creation
top_level_config_root = [f.name for f in custom_filepath.iterdir()] 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" 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 assert field in top_level_config_root
# "Corrupt" the configuration by removing the contract registry # "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 # Files and Directories
assert custom_filepath.is_dir(), 'Configuration file does not exist' assert custom_filepath.is_dir(), 'Configuration file does not exist'
assert (custom_filepath / 'keystore').is_dir(), 'KEYSTORE 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() custom_config_filepath = custom_filepath / UrsulaConfiguration.generate_filename()
assert custom_config_filepath.is_file(), 'Configuration file does not exist' 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) testerchain.time_travel(periods=1)
# The worker is valid and can be verified (even with the force option) # 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. # 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._operator_is_bonded(registry=test_registry)
assert worker._staking_provider_is_really_staking(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) 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) # ... 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 # If we force, on-chain verification, the worker is of course not verified
with pytest.raises(worker.NotStaking): 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 # Let's learn from this invalid node
lonely_blockchain_learner._current_teacher_node = worker 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 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 # Setup
mocker.patch.object(passwords, 'secret_box_decrypt', side_effect=SecretBoxAuthenticationError) 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() 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) @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') configuration_class.DEFAULT_CONFIG_ROOT = Path('/tmp')
fake_address = '0xdeadbeef' fake_address = '0xdeadbeef'
@ -121,10 +124,15 @@ def test_default_character_configuration_preservation(configuration_class, teste
domain=network, domain=network,
rest_host=MOCK_IP_ADDRESS, rest_host=MOCK_IP_ADDRESS,
payment_provider=MOCK_ETH_PROVIDER_URI, payment_provider=MOCK_ETH_PROVIDER_URI,
policy_registry=test_registry,
payment_network=TEMPORARY_DOMAIN,
keystore=keystore) keystore=keystore)
else: 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() generated_filepath = character_config.generate_filepath()
assert generated_filepath == expected_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/>. along with nucypher. If not, see <https://www.gnu.org/licenses/>.
""" """
import datetime import datetime
from base64 import b64encode, b64decode from base64 import b64encode
import maya import maya
import pytest import pytest
from nucypher_core import ( from nucypher_core import (
MessageKit as MessageKitClass, MessageKit as MessageKitClass,
EncryptedTreasureMap as EncryptedTreasureMapClass) EncryptedTreasureMap as EncryptedTreasureMapClass)
from nucypher_core.umbral import SecretKey, Signer from nucypher_core.umbral import SecretKey
from nucypher.characters.control.specifications.fields import ( from nucypher.characters.control.specifications.fields import (
DateTime, DateTime,
@ -48,6 +47,7 @@ from nucypher.control.specifications.exceptions import InvalidInputData
# assert deserialized == data # assert deserialized == data
def test_file(tmpdir): 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 " \ 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 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 You should have received a copy of the GNU Affero General Public License
along with nucypher. If not, see <https://www.gnu.org/licenses/>. along with nucypher. If not, see <https://www.gnu.org/licenses/>.
""" """
from pathlib import Path
from eth_utils import to_checksum_address
import pytest import pytest
from eth_utils import to_checksum_address
from nucypher_core import NodeMetadata, NodeMetadataPayload from nucypher_core import NodeMetadata, NodeMetadataPayload
from nucypher_core.umbral import SecretKey, Signer from nucypher_core.umbral import SecretKey, Signer
from nucypher.acumen.perception import FleetSensor 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.characters.lawful import Ursula
from nucypher.crypto.tls import generate_self_signed_certificate
from nucypher.network.exceptions import NodeSeemsToBeDown from nucypher.network.exceptions import NodeSeemsToBeDown
from nucypher.network.middleware import NucypherMiddlewareClient from nucypher.network.middleware import NucypherMiddlewareClient
from nucypher.network.nodes import TEACHER_NODES from nucypher.network.nodes import TEACHER_NODES
from nucypher.network.protocols import InterfaceInfo
from nucypher.utilities.networking import ( from nucypher.utilities.networking import (
determine_external_ip_address, determine_external_ip_address,
get_external_ip_from_centralized_source, get_external_ip_from_centralized_source,
@ -38,6 +39,7 @@ from nucypher.utilities.networking import (
from tests.constants import MOCK_IP_ADDRESS from tests.constants import MOCK_IP_ADDRESS
MOCK_NETWORK = 'holodeck' MOCK_NETWORK = 'holodeck'
MOCK_PORT = 1111
class Dummy: # Teacher class Dummy: # Teacher
@ -66,6 +68,10 @@ class Dummy: # Teacher
def rest_url(self): def rest_url(self):
return MOCK_IP_ADDRESS return MOCK_IP_ADDRESS
@property
def rest_interface(self):
return InterfaceInfo(host=MOCK_IP_ADDRESS, port=MOCK_PORT)
def metadata(self): def metadata(self):
signer = Signer(SecretKey.random()) signer = Signer(SecretKey.random())
@ -79,8 +85,8 @@ class Dummy: # Teacher
verifying_key=signer.verifying_key(), verifying_key=signer.verifying_key(),
encrypting_key=SecretKey.random().public_key(), encrypting_key=SecretKey.random().public_key(),
certificate_der=b'not a certificate', certificate_der=b'not a certificate',
host='127.0.0.1', host=MOCK_IP_ADDRESS,
port=1111, port=MOCK_PORT,
) )
return NodeMetadata(signer=signer, return NodeMetadata(signer=signer,
payload=payload) payload=payload)
@ -95,12 +101,14 @@ def mock_requests(mocker):
@pytest.fixture(autouse=True) @pytest.fixture(autouse=True)
def mock_client(mocker): 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) yield mocker.patch.object(NucypherMiddlewareClient, 'invoke_method', return_value=Dummy.GoodResponse)
@pytest.fixture(autouse=True) @pytest.fixture(autouse=True)
def mock_default_teachers(mocker): 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) 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, ursula_config = UrsulaConfiguration(**test_params,
rest_port=rest_port, rest_port=rest_port,
payment_provider=payment_provider, payment_provider=payment_provider,
payment_network=payment_network) payment_network=payment_network,
policy_registry=test_params['registry'])
return ursula_config 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 payment_network = TEMPORARY_DOMAIN if not federated else None
config = AliceConfiguration(**test_params, config = AliceConfiguration(**test_params,
payment_provider=payment_provider, payment_provider=payment_provider,
payment_network=payment_network) payment_network=payment_network,
policy_registry=test_params['registry'])
return config 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 You should have received a copy of the GNU Affero General Public License
along with nucypher. If not, see <https://www.gnu.org/licenses/>. along with nucypher. If not, see <https://www.gnu.org/licenses/>.
""" """
import time
import random import random
import os import socket
import time
from pathlib import Path
import requests import requests
import socket
from constant_sorrow.constants import CERTIFICATE_NOT_SAVED
from flask import Response from flask import Response
from nucypher_core import MetadataRequest, FleetStateChecksum from nucypher_core import MetadataRequest, FleetStateChecksum
from nucypher.characters.lawful import Ursula from nucypher.characters.lawful import Ursula
from nucypher.config.constants import TEMPORARY_DOMAIN
from nucypher.network.middleware import NucypherMiddlewareClient, RestMiddleware from nucypher.network.middleware import NucypherMiddlewareClient, RestMiddleware
from tests.utils.ursula import MOCK_KNOWN_URSULAS_CACHE 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) mock_client = self._get_mock_client_by_port(port)
else: else:
raise ValueError("You need to pass either the node or a host and port.") raise ValueError("You need to pass either the node or a host and port.")
host, port = node.rest_interface.host, node.rest_interface.port
# We don't use certs in mock-style tests anyway. return host, port, mock_client
return node.rest_url(), CERTIFICATE_NOT_SAVED, mock_client
def invoke_method(self, method, url, *args, **kwargs): def invoke_method(self, method, url, *args, **kwargs):
_cert_location = kwargs.pop("verify") # TODO: Is this something that can be meaningfully tested? _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): def clean_params(self, request_kwargs):
request_kwargs["query_string"] = request_kwargs.pop("params", {}) 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): class MockRestMiddleware(RestMiddleware):
_ursulas = None _ursulas = None
@ -98,11 +100,6 @@ class MockRestMiddleware(RestMiddleware):
class NotEnoughMockUrsulas(Ursula.NotEnoughUrsulas): class NotEnoughMockUrsulas(Ursula.NotEnoughUrsulas):
pass 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): class MockRestMiddlewareForLargeFleetTests(MockRestMiddleware):
""" """