mirror of https://github.com/nucypher/nucypher.git
Merge pull request #2873 from KPrasch/airship
Grant & Retrieve via Polygon Payments; Pre-release cleanup.pull/2889/head
commit
b89a3195d4
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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.
|
|
@ -0,0 +1,2 @@
|
|||
Extend policy probationary period to 2022-6-16T23:59:59.0Z.
|
||||
|
|
@ -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."""
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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):
|
||||
"""
|
||||
|
|
Loading…
Reference in New Issue